import logging
import os

# [setup_trusted_values_dict] takes in a path [path], and creates the file [trusted_values_dict.py] in the
# directory specified in [path]. If [trusted_values_dict.py] already exists within this directory,
# nothing happens.

# Called by NRPyUnitTests_(Anything)_Globals

def setup_trusted_values_dict(path):

    # Try opening [trusted_values_dict.py] in [directory].
    try:
        logging.debug(' Trying to open {}/trusted_values_dict.py...'.format(path))
        fr = open(os.path.join(path, 'trusted_values_dict.py'), 'r')
        logging.debug(' ...Success, file already exists.')
        fr.close()
    # If [trusted_values_dict.py] does not exist in [directory], create it with default content.
    except IOError:
        logging.info(' ...trusted_values_dict.py does not exist. Creating it...')
        fw = open(os.path.join(path, 'trusted_values_dict.py'), 'w+')
        fw.write('from mpmath import mpf, mp, mpc\nfrom UnitTesting.standard_constants import precision\n\n'
                 'mp.dps = precision\ntrusted_values_dict = {}\n')
        fw.close()
        logging.info(' ...Success: trusted_values_dict.py created.\n')


# coding: utf-8

# # АОТ ДЗ №2

# ### Михаил Ховричев, БПМ131
# #### Вариант E2

# Разработана программа по тематической классификации текста *с использованием машинного обучения* с помощью **word2vec**.
# В качестве размеченных данных были взяты материалы с сайта http://www.world-art.ru/. Скрипт для скрэпинга материалов -- в файле parser.py
# Данные на русском языке (в нужном объёме) довольно сложно получить. Так, например, данный сайт оказался одним из немногих, которые вообще позволили скрэпить данные, но процесс крайне осложнён -- каждые несколько запросов сайт банит по IP на полчаса. Рекомендую это учесть и сразу давать данные для обучения.
#
# В разработанной программе частично использованы свободные материалы с семинара Яндекса.
# https://events.yandex.ru/lib/talks/4137/

# In[415]:

import csv
import pymorphy2
import nltk
from nltk import word_tokenize
from gensim.models import Word2Vec
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, confusion_matrix
from itertools import islice
from sklearn import linear_model
import gensim
from sklearn.neighbors import KNeighborsClassifier
import re

get_ipython().magic('matplotlib inline')

# In[393]:

"""функция для графического отображения матрицы возмущений"""
def plot_confusion_matrix(cm, title='Confusion matrix', cmap=plt.cm.Blues):
    plt.imshow(cm, interpolation='nearest', cmap=cmap) # интерполяция методом NN, как в диаграмме Вороного
    plt.title(title)
    plt.colorbar()
    tick_marks = np.arange(len(genres))
    target_names = genres
    plt.xticks(tick_marks, target_names, rotation=90)
    plt.yticks(tick_marks, target_names)
    plt.tight_layout()
    plt.ylabel('True genre')
    plt.xlabel('Predicted genre')

# In[395]:

"""функция для расчёта точности и построения текстовой матрицы возмущения"""
def evaluate_prediction(predictions, target, title='Confusion matrix'):
    print('accuracy %s' % accuracy_score(target, predictions))
    cm = confusion_matrix(target, predictions)
    print('confusion matrix\n %s' % cm)
    print('(row=expected, col=predicted)')

    cm_normalized = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]
    plot_confusion_matrix(cm_normalized, title + ' Normalized')

# In[443]:

"""
Функция расчёта усреднённого вектора документа.
В word2vec каждому слову приписывается его векторное представление.
Нам же нужно получить векторное представление документа.
"""
def word_averaging(wv, words):
    all_words = set()
    mean = []

    for word in words:
        if isinstance(word, np.ndarray):
            mean.append(word)
        elif word in wv.vocab:
            mean.append(wv.syn0norm[wv.vocab[word].index])
            all_words.add(wv.vocab[word].index)

    if not mean:
        return np.zeros(wv.layer_size,)

    mean = gensim.matutils.unitvec(np.array(mean).mean(axis=0)).astype(np.float32) # нормировка
    return mean

# In[445]:

"""
Расчёт усреднённого вектора для каждого документа в датасете.
"""
def  word_averaging_list(wv, text_list):
    return np.vstack([word_averaging(wv, review) for review in text_list])

# In[444]:

"""
Функция токенизации.
NLTK не распознаёт кавычки-ёлочки, поэтому отдельно отчищаем их от токенов.
"""
def my_tokenizer(text):
    tokens = []
    for sent in nltk.sent_tokenize(text):
        for word in nltk.word_tokenize(sent):
            if '«' or '»' in word:
                word = re.sub('[\«\»]', '', word)
            if len(word) < 2:
                continue
            tokens.append(word)
    return tokens

# In[390]:

# Как и во всех моих ДЗ, будем приводить документы к стандартному представлению с помощью связки NLTK+pymorphy2
from nltk.corpus import stopwords
sw = stopwords.words('russian')
badPOS = {'NPRO','PREP', 'CONJ', 'PRCL', 'INTJ','PNCT', 'NUMB', 'ROMN', 'UNKN', 'Apro'}
morph = pymorphy2.MorphAnalyzer()

# In[391]:

pd.set_option('display.max_colwidth', 80) # для отображения

# Ознакомимся с данными. Это сюжеты аниме с отмеченным жанром. Для работы с данными и машинного обучения используем классическую связку (pandas, numpy, matplotlib, scikit-learn).

# In[374]:

path = 'data/anime_data.csv'
df = pd.read_table(path)
df = df.dropna()
df.head()

# In[429]:

df.shape

# In[433]:

print("Количество слов в корпусе:", df['plot'].apply(lambda x: len(x.split(' '))).sum())

# Как видим, корпус довольно небольшой.

# In[375]:

genres = df['genre'].unique()
print('Количество жанров:', len(genres))
print(genres)

# In[392]:

df['plot'].apply(lambda x: len(x.split(' '))).sum()
df.genre.value_counts().plot(kind="bar", width=0.8, rot=90, title='Распределение жанров в датасете', color='g')
# удельная доля жанра "приключения" (baseline)
df.genre.value_counts()['приключения']/df.genre.value_counts().sum()

# Разделим данные на обучающую и контрольную выборки в соотношении 90%-10%.

# In[377]:

train_data, test_data = train_test_split(df, test_size=0.1, random_state=330)
len(test_data)

# In[378]:

test_data.genre.value_counts().plot(kind="bar",width=0.8,rot=90, title='Распределение жанров в тест-сете', color='g')
test_data.genre.value_counts()['приключения']/test_data.genre.value_counts().sum()

# Как видим, доля жанра "приключения" в тестовой выборке составляет порядка 19%.

# Произведём токенизацию с помощью функции my_tokenizer.

# In[385]:

test_tokenized = test_data.apply(lambda x: my_tokenizer(x['plot']), axis=1).values
train_tokenized = train_data.apply(lambda x: my_tokenizer(x['plot']), axis=1).values

# Произведём лемматизацию. Аналогичная производилась и в предыдущих ДЗ.

# In[386]:

transform = lambda x: morph.parse(x)[0]
condition = lambda x: not [item for item in badPOS if item in str(transform(x).tag)] and transform(x).word not in sw

test_lemmatized = [[transform(token).normal_form for token in doc if condition(token)] for doc in test_tokenized]
train_lemmatized = [[transform(token).normal_form for token in doc if condition(token)] for doc in train_tokenized]

# Теперь будем обучать модель. Для этого объединим нормализованные выборки.
# Для имплементации word2vec используем gensim.

# In[527]:

sentences = test_lemmatized+train_lemmatized
model = Word2Vec(sentences, size=150)
model.init_sims(replace=True)

# Посмотрим на модель.

# In[528]:

model.most_similar("любовь")

# Приведём каждый документ в выборках к векторному представлению с помощью функций word_averaging, word_averaging_list.

# In[529]:

test_WA = word_averaging_list(model, test_tokenized)
train_WA = word_averaging_list(model, train_tokenized)

# Воспользуемся логистической регрессией для предсказания. Логистическая регрессия выбрана, как показывающая немного лучшие результаты в сравнении с тем же методом ближайшего соседа.

# In[536]:

logreg = linear_model.LogisticRegression(n_jobs=1, C=1e6)
logreg = logreg.fit(train_WA, train_data['genre'])
predicted = logreg.predict(test_WA)

# Посмотрим, что у нас получилось. В матрице возмущений в текстовом виде подсчёт идёт по количествам, в графическом -- по удельной доле. Концентрироваться числам (или синим квадратикам) нужно на диагонали. Большее число (интенсивность цвета) на диагонали -- хорошо, вне диагонали -- плохо.

# In[537]:

evaluate_prediction(predicted, test_data.genre)

# Не очень здорово. 21% -- это лучше, чем 19%, но незначительно. (Однако это не программа плохая, так что невысокая точность, как мне кажется, не повод для снижения оценки). Что является причиной?
#
# 1. Размер корпуса. Он действительно небольшой, а достать данные оказалось не такой простой задачей (для sentiment analysis это сделать намного проще, но анализ тональности, хоть и подзадача данной задачи, -- это другой вариант ДЗ).
#
# 2. В изначальных данных было больше классов ("фэнтези", "сёдзе-ай" и др.), но они имели от 1 до 3 представителей, что создавало сильный дисбаланс в данных (многие  жанры даже не попадали в тестовую выборку), поэтому подобные сюжеты были присоединены к имеющимся по семантике ("фэнтези" -> "фантастика", "сёдзе-ай" -> "романтика" и т.д). Возможно, этого делать не стоило, однако несбалансированные данные -- это хуже.
#
# 3. Аниме по сути своей достаточно эмоциональный вид анимации, в нём часто обыгрываются отношения между людьми, поэтому лексически описание "романтических" сюжетов мало отличается от "повседневных". Это видим и в матрице -- классификатор их путает.
#
# Теперь отвлечёмся от грустных мыслей о точности модели и исследуем результат.
#
# "Фантастика" часто определяется как "комедия" (а наоборот классификатор не путает).
# "Повседневность" определяется относительно хорошо, но "повседневные" жанры предсказываются как что угодно.
# "Романтика" часто определяется как "повседневность".
# "Драма" часто путается с "комедией". Причём в обе стороны.
# "Приключения" определяются лучше, чем всё остальное. Но, как ни странно, совершенно не путаются с "фантастикой".
# "Комедия" часто определяется, как "романтика" и "драма". Первое ещё как-то объяснимо, второе -- неожиданно.
#
# В целом, программа составлена верно, учтены все аспекты предобработки текста, удалены все стоп-слова, произведена лемматизация, результаты . Использовано машинное обучение. Осталось взять побольше данных, и результат будет точным.


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

import numpy as np
import pytest

from mindspore import log
import mindspore.dataset as ds
import mindspore.dataset.text as text
import mindspore.dataset.text.transforms as T

DATASET_ROOT_PATH = "../data/dataset/testVectors/"

def test_vectors_all_tovectors_params_eager():
    """
    Feature: Vectors
    Description: test with all parameters which include `unk_init`
        and `lower_case_backup` in function ToVectors in eager mode
    Expectation: output is equal to the expected value
    """
    vectors = text.Vectors.from_file(DATASET_ROOT_PATH + "vectors.txt", max_vectors=4)
    myUnk = [-1, -1, -1, -1, -1, -1]
    to_vectors = T.ToVectors(vectors, unk_init=myUnk, lower_case_backup=True)
    result1 = to_vectors("Ok")
    result2 = to_vectors("!")
    result3 = to_vectors("This")
    result4 = to_vectors("is")
    result5 = to_vectors("my")
    result6 = to_vectors("home")
    result7 = to_vectors("none")
    res = [[0.418, 0.24968, -0.41242, 0.1217, 0.34527, -0.04445718411],
           [0.013441, 0.23682, -0.16899, 0.40951, 0.63812, 0.47709],
           [0.15164, 0.30177, -0.16763, 0.17684, 0.31719, 0.33973],
           [0.70853, 0.57088, -0.4716, 0.18048, 0.54449, 0.72603],
           [-1, -1, -1, -1, -1, -1],
           [-1, -1, -1, -1, -1, -1],
           [-1, -1, -1, -1, -1, -1]]
    res_array = np.array(res, dtype=np.float32)

    assert np.array_equal(result1, res_array[0])
    assert np.array_equal(result2, res_array[1])
    assert np.array_equal(result3, res_array[2])
    assert np.array_equal(result4, res_array[3])
    assert np.array_equal(result5, res_array[4])
    assert np.array_equal(result6, res_array[5])
    assert np.array_equal(result7, res_array[6])

def test_vectors_from_file():
    """
    Feature: Vectors
    Description: test with only default parameter
    Expectation: output is equal to the expected value
    """
    vectors = text.Vectors.from_file(DATASET_ROOT_PATH + "vectors.txt")
    to_vectors = text.ToVectors(vectors)
    data = ds.TextFileDataset(DATASET_ROOT_PATH + "words.txt", shuffle=False)
    data = data.map(operations=to_vectors, input_columns=["text"])
    ind = 0
    res = [[0.418, 0.24968, -0.41242, 0.1217, 0.34527, -0.04445718411],
           [0, 0, 0, 0, 0, 0],
           [0.15164, 0.30177, -0.16763, 0.17684, 0.31719, 0.33973],
           [0.70853, 0.57088, -0.4716, 0.18048, 0.54449, 0.72603],
           [0.68047, -0.039263, 0.30186, -0.17792, 0.42962, 0.032246],
           [0.26818, 0.14346, -0.27877, 0.016257, 0.11384, 0.69923],
           [0, 0, 0, 0, 0, 0]]
    for d in data.create_dict_iterator(num_epochs=1, output_numpy=True):
        res_array = np.array(res[ind], dtype=np.float32)
        assert np.array_equal(res_array, d["text"]), ind
        ind += 1

def test_vectors_from_file_all_buildfromfile_params():
    """
    Feature: Vectors
    Description: test with all parameters which include `path` and `max_vector` in function BuildFromFile
    Expectation: output is equal to the expected value
    """
    vectors = text.Vectors.from_file(DATASET_ROOT_PATH + "vectors.txt", max_vectors=100)
    to_vectors = text.ToVectors(vectors)
    data = ds.TextFileDataset(DATASET_ROOT_PATH + "words.txt", shuffle=False)
    data = data.map(operations=to_vectors, input_columns=["text"])
    ind = 0
    res = [[0.418, 0.24968, -0.41242, 0.1217, 0.34527, -0.04445718411],
           [0, 0, 0, 0, 0, 0],
           [0.15164, 0.30177, -0.16763, 0.17684, 0.31719, 0.33973],
           [0.70853, 0.57088, -0.4716, 0.18048, 0.54449, 0.72603],
           [0.68047, -0.039263, 0.30186, -0.17792, 0.42962, 0.032246],
           [0.26818, 0.14346, -0.27877, 0.016257, 0.11384, 0.69923],
           [0, 0, 0, 0, 0, 0]]
    for d in data.create_dict_iterator(num_epochs=1, output_numpy=True):
        res_array = np.array(res[ind], dtype=np.float32)
        assert np.array_equal(res_array, d["text"]), ind
        ind += 1

def test_vectors_from_file_all_buildfromfile_params_eager():
    """
    Feature: Vectors
    Description: test with all parameters which include `path` and `max_vector` in function BuildFromFile in eager mode
    Expectation: output is equal to the expected value
    """
    vectors = text.Vectors.from_file(DATASET_ROOT_PATH + "vectors.txt", max_vectors=4)
    to_vectors = T.ToVectors(vectors)
    result1 = to_vectors("ok")
    result2 = to_vectors("!")
    result3 = to_vectors("this")
    result4 = to_vectors("is")
    result5 = to_vectors("my")
    result6 = to_vectors("home")
    result7 = to_vectors("none")
    res = [[0.418, 0.24968, -0.41242, 0.1217, 0.34527, -0.04445718411],
           [0.013441, 0.23682, -0.16899, 0.40951, 0.63812, 0.47709],
           [0.15164, 0.30177, -0.16763, 0.17684, 0.31719, 0.33973],
           [0.70853, 0.57088, -0.4716, 0.18048, 0.54449, 0.72603],
           [0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0]]
    res_array = np.array(res, dtype=np.float32)

    assert np.array_equal(result1, res_array[0])
    assert np.array_equal(result2, res_array[1])
    assert np.array_equal(result3, res_array[2])
    assert np.array_equal(result4, res_array[3])
    assert np.array_equal(result5, res_array[4])
    assert np.array_equal(result6, res_array[5])
    assert np.array_equal(result7, res_array[6])

def test_vectors_from_file_eager():
    """
    Feature: Vectors
    Description: test with only default parameter in eager mode
    Expectation: output is equal to the expected value
    """
    vectors = text.Vectors.from_file(DATASET_ROOT_PATH + "vectors.txt")
    to_vectors = T.ToVectors(vectors)
    result1 = to_vectors("ok")
    result2 = to_vectors("!")
    result3 = to_vectors("this")
    result4 = to_vectors("is")
    result5 = to_vectors("my")
    result6 = to_vectors("home")
    result7 = to_vectors("none")
    res = [[0.418, 0.24968, -0.41242, 0.1217, 0.34527, -0.04445718411],
           [0.013441, 0.23682, -0.16899, 0.40951, 0.63812, 0.47709],
           [0.15164, 0.30177, -0.16763, 0.17684, 0.31719, 0.33973],
           [0.70853, 0.57088, -0.4716, 0.18048, 0.54449, 0.72603],
           [0.68047, -0.039263, 0.30186, -0.17792, 0.42962, 0.032246],
           [0.26818, 0.14346, -0.27877, 0.016257, 0.11384, 0.69923],
           [0, 0, 0, 0, 0, 0]]
    res_array = np.array(res, dtype=np.float32)

    assert np.array_equal(result1, res_array[0])
    assert np.array_equal(result2, res_array[1])
    assert np.array_equal(result3, res_array[2])
    assert np.array_equal(result4, res_array[3])
    assert np.array_equal(result5, res_array[4])
    assert np.array_equal(result6, res_array[5])
    assert np.array_equal(result7, res_array[6])

def test_vectors_invalid_input():
    """
    Feature: Vectors
    Description: test the validate function with invalid parameters.
    Expectation:
    """
    def test_invalid_input(test_name, file_path, error, error_msg, max_vectors=None,
                           unk_init=None, lower_case_backup=False, token="ok"):
        log.info("Test Vectors with wrong input: {0}".format(test_name))
        with pytest.raises(error) as error_info:
            vectors = text.Vectors.from_file(file_path, max_vectors=max_vectors)
            to_vectors = T.ToVectors(vectors, unk_init=unk_init, lower_case_backup=lower_case_backup)
            to_vectors(token)
        assert error_msg in str(error_info.value)

    test_invalid_input("Not all vectors have the same number of dimensions",
                       DATASET_ROOT_PATH + "vectors_dim_different.txt", error=RuntimeError,
                       error_msg="all vectors must have the same number of dimensions, but got dim 5 while expecting 6")
    test_invalid_input("the file is empty.", DATASET_ROOT_PATH + "vectors_empty.txt",
                       error=RuntimeError, error_msg="invalid file, file is empty.")
    test_invalid_input("the count of `unknown_init`'s element is different with word vector.",
                       DATASET_ROOT_PATH + "vectors.txt",
                       error=RuntimeError, error_msg="Unexpected error. ToVectors: " +
                       "unk_init must be the same length as vectors, but got unk_init: 2 and vectors: 6",
                       unk_init=[-1, -1])
    test_invalid_input("The file not exist", DATASET_ROOT_PATH + "not_exist.txt", error=RuntimeError,
                       error_msg="get real path failed")
    test_invalid_input("The token is 1-dimensional",
                       DATASET_ROOT_PATH + "vectors_with_wrong_info.txt", error=RuntimeError,
                       error_msg="token with 1-dimensional vector.")
    test_invalid_input("max_vectors parameter must be greater than 0",
                       DATASET_ROOT_PATH + "vectors.txt", error=ValueError,
                       error_msg="Input max_vectors is not within the required interval", max_vectors=-1)
    test_invalid_input("invalid max_vectors parameter type as a float",
                       DATASET_ROOT_PATH + "vectors.txt", error=TypeError,
                       error_msg="Argument max_vectors with value 1.0 is not of type [<class 'int'>],"
                       " but got <class 'float'>.", max_vectors=1.0)
    test_invalid_input("invalid max_vectors parameter type as a string",
                       DATASET_ROOT_PATH + "vectors.txt", error=TypeError,
                       error_msg="Argument max_vectors with value 1 is not of type [<class 'int'>],"
                       " but got <class 'str'>.", max_vectors="1")
    test_invalid_input("invalid token parameter type as a float", DATASET_ROOT_PATH + "vectors.txt", error=RuntimeError,
                       error_msg="input tensor type should be string.", token=1.0)
    test_invalid_input("invalid lower_case_backup parameter type as a string", DATASET_ROOT_PATH + "vectors.txt",
                       error=TypeError, error_msg="Argument lower_case_backup with " +
                       "value True is not of type [<class 'bool'>],"
                       " but got <class 'str'>.", lower_case_backup="True")
    test_invalid_input("invalid lower_case_backup parameter type as a string", DATASET_ROOT_PATH + "vectors.txt",
                       error=TypeError, error_msg="Argument lower_case_backup with " +
                       "value True is not of type [<class 'bool'>],"
                       " but got <class 'str'>.", lower_case_backup="True")

if __name__ == '__main__':
    test_vectors_all_tovectors_params_eager()
    test_vectors_from_file()
    test_vectors_from_file_all_buildfromfile_params()
    test_vectors_from_file_all_buildfromfile_params_eager()
    test_vectors_from_file_eager()
    test_vectors_invalid_input()


# -*- coding: utf-8 -*-
"""
Created on Sun Jun 11 09:56:39 2017

@author: Raghav Bali
"""

"""

This script visualizes data using matplotlib

``Execute``
        $ python matplotlib_viz.py

"""

import numpy as np
import matplotlib.pyplot as plt

if __name__=='__main__':

    # sample plot
    x = np.linspace(-10, 10, 50)
    y=np.sin(x)

    plt.plot(x,y)
    plt.title('Sine Curve using matplotlib')
    plt.xlabel('x-axis')
    plt.ylabel('y-axis')
    plt.show()

    # figure
    plt.figure(1)
    plt.plot(x,y)
    plt.title('Fig1: Sine Curve')
    plt.xlabel('x-axis')
    plt.ylabel('y-axis')
    plt.show()

    plt.figure(2)
    y=np.cos(x)
    plt.plot(x,y)
    plt.title('Fig2: Cosine Curve')
    plt.xlabel('x-axis')
    plt.ylabel('y-axis')
    plt.show()

    ### subplot

    # fig.add_subplot
    y = np.sin(x)
    figure_obj = plt.figure()
    ax1 = figure_obj.add_subplot(2,2,1)
    ax1.plot(x,y)

    ax2 = figure_obj.add_subplot(2,2,2)
    ax3 = figure_obj.add_subplot(2,2,3)

    ax4 = figure_obj.add_subplot(2,2,4)
    ax4.plot(x+10,y)
    plt.show()

    # plt.subplots
    fig, ax_list = plt.subplots(2,1,sharex=True)
    y= np.sin(x)
    ax_list[0].plot(x,y)

    y= np.cos(x)
    ax_list[1].plot(x,y)
    plt.show()

    # plt.subplot (creates figure and axes objects automatically)
    plt.subplot(2,2,1)
    y = np.sin(x)
    plt.plot(x,y)

    plt.subplot(2,2,2)
    y = np.cos(x)
    plt.plot(x,y)

    plt.subplot(2,1,2)
    y = np.tan(x)
    plt.plot(x,y)

    plt.show()

    # subplot2grid
    y = np.abs(x)
    z = x**2

    plt.subplot2grid((4,3), (0, 0), rowspan=4, colspan=2)
    plt.plot(x, y,'b',x,z,'r')

    ax2 = plt.subplot2grid((4,3), (0, 2),rowspan=2)
    plt.plot(x, y,'b')
    plt.setp(ax2.get_xticklabels(), visible=False)

    plt.subplot2grid((4,3), (2, 2), rowspan=2)
    plt.plot(x, z,'r')

    plt.show()

    ### formatting

    y = x

    # color
    ax1 = plt.subplot(611)
    plt.plot(x,y,color='green')
    ax1.set_title('Line Color')
    plt.setp(ax1.get_xticklabels(), visible=False)

    # linestyle
    # linestyles -> '-','--','-.', ':', 'steps'
    ax2 = plt.subplot(612,sharex=ax1)
    plt.plot(x,y,linestyle='--')
    ax2.set_title('Line Style')
    plt.setp(ax2.get_xticklabels(), visible=False)

    # marker
    # markers -> '+', 'o', '*', 's', ',', '.', etc
    ax3 = plt.subplot(613,sharex=ax1)
    plt.plot(x,y,marker='*')
    ax3.set_title('Point Marker')
    plt.setp(ax3.get_xticklabels(), visible=False)

    # line width
    ax4 = plt.subplot(614,sharex=ax1)
    line = plt.plot(x,y)
    line[0].set_linewidth(3.0)
    ax4.set_title('Line Width')
    plt.setp(ax4.get_xticklabels(), visible=False)

    # alpha
    ax5 = plt.subplot(615,sharex=ax1)
    alpha = plt.plot(x,y)
    alpha[0].set_alpha(0.3)
    ax5.set_title('Line Alpha')
    plt.setp(ax5.get_xticklabels(), visible=False)

    # combine linestyle
    ax6 = plt.subplot(616,sharex=ax1)
    plt.plot(x,y,'b^')
    ax6.set_title('Styling Shorthand')

    fig = plt.gcf()
    fig.set_figheight(15)
    plt.show()

    # legends
    y = x**2
    z = x

    plt.plot(x,y,'g',label='y=x^2')
    plt.plot(x,z,'b:',label='y=x')
    plt.legend(loc="best")
    plt.title('Legend Sample')
    plt.show()

    # legend with latex formatting
    plt.plot(x,y,'g',label='$y = x^2$')
    plt.plot(x,z,'b:',linewidth=3,label='$y = x^2$')
    plt.legend(loc="best",fontsize='x-large')
    plt.title('Legend with LaTEX formatting')
    plt.show()

    ## axis controls
    # secondary y-axis
    fig, ax1 = plt.subplots()
    ax1.plot(x,y,'g')
    ax1.set_ylabel(r"primary y-axis", color="green")

    ax2 = ax1.twinx()

    ax2.plot(x,z,'b:',linewidth=3)
    ax2.set_ylabel(r"secondary y-axis", color="blue")

    plt.title('Secondary Y Axis')
    plt.show()

    # ticks
    y = np.log(x)
    z = np.log2(x)
    w = np.log10(x)

    plt.plot(x,y,'r',x,z,'g',x,w,'b')
    plt.title('Default Axis Ticks')
    plt.show()

    # axis-controls
    plt.plot(x,y,'r',x,z,'g',x,w,'b')
    # values: tight, scaled, equal,auto
    plt.axis('tight')
    plt.title('Tight Axis')
    plt.show()

    # manual
    plt.plot(x,y,'r',x,z,'g',x,w,'b')
    plt.axis([0,2,-1,2])
    plt.title('Manual Axis Range')
    plt.show()

    # Manual ticks
    plt.plot(x, y)
    ax = plt.gca()
    ax.xaxis.set_ticks(np.arange(-2, 2, 1))
    plt.grid(True)
    plt.title("Manual ticks on the x-axis")
    plt.show()

    # minor ticks
    plt.plot(x, z)
    plt.minorticks_on()
    ax = plt.gca()
    ax.yaxis.set_ticks(np.arange(0, 5))
    ax.yaxis.set_ticklabels(["min", 2, 4, "max"])
    plt.title("Minor ticks on the y-axis")
    plt.show()

    # scaling
    plt.plot(x, y)
    ax = plt.gca()
    # values: log, logit, symlog
    ax.set_yscale("log")
    plt.grid(True)
    plt.title("Log Scaled Axis")
    plt.show()

    # annotations
    y = x**2
    min_x = 0
    min_y = min_x**2

    plt.plot(x, y, "b-", min_x, min_y, "ro")
    plt.axis([-10,10,-25,100])

    plt.text(0, 60, "Parabola\n$y = x^2$", fontsize=15, ha="center")
    plt.text(min_x, min_y+2, "Minima", ha="center")
    plt.text(min_x, min_y-6, "(%0.1f, %0.1f)"%(min_x, min_y), ha='center',color='gray')
    plt.title("Annotated Plot")
    plt.show()

    # global formatting params
    params = {'legend.fontsize': 'large',
              'figure.figsize': (10, 10),
             'axes.labelsize': 'large',
             'axes.titlesize':'large',
             'xtick.labelsize':'large',
             'ytick.labelsize':'large'}

    plt.rcParams.update(params)

    # saving
    #plt.savefig("sample_plot.png", transparent=True)


import random

import torch
import numpy as np

def seed_generators(seed: int):
  r"""
  Seed random number generators from various packages.

  :param int seed: The seed to initialise.
  """

  random.seed(seed)
  np.random.seed(seed)
  torch.random.manual_seed(seed)

  if torch.cuda.is_available():
    torch.cuda.manual_seed_all(seed)


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# @Date    : Apr-30-20 21:07
# @Author  : Your Name (you@example.org)
# @Link    : http://example.org

import os
import numpy as np

def main():
    a = np.asarray([1, 2, 3])
    b = 1
    print(a-b)

if __name__ == "__main__":
    main()


#!/usr/bin/env python3

"""
This example shows usage of ImageManip to crop a rotated rectangle area on a frame,
or perform various image transforms: rotate, mirror, flip, perspective transform.
"""

import depthai as dai
import cv2
import numpy as np

keyRotateDecr = 'z'
keyRotateIncr = 'x'
keyResizeInc = 'v'
keyWarpTestCycle = 'c'

def printControls():
    print("=== Controls:")
    print(keyRotateDecr, "-rotated rectangle crop, decrease rate")
    print(keyRotateIncr, "-rotated rectangle crop, increase rate")
    print(keyWarpTestCycle, "-warp 4-point transform, cycle through modes")
    print(keyResizeInc, "-resize cropped region, or disable resize")
    print("h -print controls (help)")

rotateRateMax = 5.0
rotateRateInc = 0.1

resizeMaxW = 800
resizeMaxH = 600
resizeFactorMax = 5

'''
The crop points are specified in clockwise order,
with first point mapped to output top-left, as:
    P0  ->  P1
     ^       v
    P3  <-  P2
'''
P0 = [0, 0]  # top-left
P1 = [1, 0]  # top-right
P2 = [1, 1]  # bottom-right
P3 = [0, 1]  # bottom-left

warpList = [
    # points order, normalized cordinates, description
    # [[[0, 0], [1, 0], [1, 1], [0, 1]], True, "passthrough"],
    # [[[0, 0], [639, 0], [639, 479], [0, 479]], False, "passthrough (pixels)"],
    [[P0, P1, P2, P3], True, "1. passthrough"],
    [[P3, P0, P1, P2], True, "2. rotate 90"],
    [[P2, P3, P0, P1], True, "3. rotate 180"],
    [[P1, P2, P3, P0], True, "4. rotate 270"],
    [[P1, P0, P3, P2], True, "5. horizontal mirror"],
    [[P3, P2, P1, P0], True, "6. vertical flip"],
    [[[-0.1, -0.1], [1.1, -0.1], [1.1, 1.1], [-0.1, 1.1]], True, "7. add black borders"],
    [[[-0.3, 0], [1, 0], [1.3, 1], [0, 1]], True, "8. parallelogram transform"],
    [[[-0.2, 0], [1.8, 0], [1, 1], [0, 1]], True, "9. trapezoid transform"],
]

# Create pipeline
pipeline = dai.Pipeline()

# Define sources and outputs
camRgb = pipeline.createColorCamera()
manip = pipeline.createImageManip()

camOut = pipeline.createXLinkOut()
manipOut = pipeline.createXLinkOut()
manipCfg = pipeline.createXLinkIn()

camOut.setStreamName("preview")
manipOut.setStreamName("manip")
manipCfg.setStreamName("manipCfg")

# Properties
camRgb.setPreviewSize(640, 480)
camRgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1080_P)
camRgb.setInterleaved(False)
camRgb.setColorOrder(dai.ColorCameraProperties.ColorOrder.BGR)
manip.setMaxOutputFrameSize(2000 * 1500 * 3)

# Linking
camRgb.preview.link(camOut.input)
camRgb.preview.link(manip.inputImage)
manip.out.link(manipOut.input)
manipCfg.out.link(manip.inputConfig)

# Connect to device and start pipeline
with dai.Device(pipeline) as device:

    # Create input & output queues
    qPreview = device.getOutputQueue(name="preview", maxSize=4)
    qManip = device.getOutputQueue(name="manip", maxSize=4)
    qManipCfg = device.getInputQueue(name="manipCfg")

    key = -1
    angleDeg = 0
    rotateRate = 1.0
    resizeFactor = 0
    resizeX = 0
    resizeY = 0
    testFourPt = False
    warpIdx = -1

    printControls()

    while key != ord('q'):
        if key > 0:
            print("Pressed: ", key)
            if key == ord(keyRotateDecr) or key == ord(keyRotateIncr):
                if key == ord(keyRotateDecr):
                    if rotateRate > -rotateRateMax:
                        rotateRate -= rotateRateInc
                if key == ord(keyRotateIncr):
                    if rotateRate < rotateRateMax:
                        rotateRate += rotateRateInc
                testFourPt = False
                print("Crop rotated rectangle, rate per frame: {:.1f} degrees".format(rotateRate))
            elif key == ord(keyResizeInc):
                resizeFactor += 1
                if resizeFactor > resizeFactorMax:
                    resizeFactor = 0
                    print("Crop region not resized")
                else:
                    resizeX = resizeMaxW // resizeFactor
                    resizeY = resizeMaxH // resizeFactor
                    print("Crop region resized to: ", resizeX, 'x', resizeY)
            elif key == ord(keyWarpTestCycle):
                # Disable resizing initially
                resizeFactor = 0
                warpIdx = (warpIdx + 1) % len(warpList)
                testFourPt = True
                testDescription = warpList[warpIdx][2]
                print("Warp 4-point transform: ", testDescription)
            elif key == ord('h'):
                printControls()

        # Send an updated config with continuous rotate, or after a key press
        if key >= 0 or (not testFourPt and abs(rotateRate) > 0.0001):
            cfg = dai.ImageManipConfig()
            if testFourPt:
                test = warpList[warpIdx]
                points, normalized = test[0], test[1]
                point2fList = []
                for p in points:
                    pt = dai.Point2f()
                    pt.x, pt.y = p[0], p[1]
                    point2fList.append(pt)
                cfg.setWarpTransformFourPoints(point2fList, normalized)
            else:
                angleDeg += rotateRate
                rotatedRect = ((320, 240), (400, 400), angleDeg)
                rr = dai.RotatedRect()
                rr.center.x, rr.center.y = rotatedRect[0]
                rr.size.width, rr.size.height = rotatedRect[1]
                rr.angle = rotatedRect[2]
                cfg.setCropRotatedRect(rr, False)
            if resizeFactor > 0:
                cfg.setResize(resizeX, resizeY)
            # cfg.setWarpBorderFillColor(255, 0, 0)
            # cfg.setWarpBorderReplicatePixels()
            qManipCfg.send(cfg)

        for q in [qPreview, qManip]:
            pkt = q.get()
            name = q.getName()
            shape = (3, pkt.getHeight(), pkt.getWidth())
            frame = pkt.getCvFrame()
            if name == "preview" and not testFourPt:
                # Draw RotatedRect cropped area on input frame
                points = np.int0(cv2.boxPoints(rotatedRect))
                cv2.drawContours(frame, [points], 0, (255, 0, 0), 1)
                # Mark top-left corner
                cv2.circle(frame, tuple(points[1]), 10, (255, 0, 0), 2)
            cv2.imshow(name, frame)
        key = cv2.waitKey(1)


from torchtext import vocab, data
import numpy as np
from niklib.utils import pad_sequences
#from torchtext.datasets import language_modeling
#from spacy import spacy
# Download spacy package if does not have
#!python -m spacy download en
import string
import re
from falib.text import Tokenizer

class TextProcessor(object):

    def __init__(self, w2v, tokenize_fn=None, max_pad=50):
        self.w2v, self.tokenize_fn, self.max_pad = w2v, tokenize_fn, max_pad
        self.re_tok = re.compile(f'([{string.punctuation}“”¨«»®´·º½¾¿¡§£₤‘’])')
        self.fastai_tok = Tokenizer()
        if tokenize_fn is None:
            TEXT = data.Field(lower=True, tokenize=self.tokenize)
            self.tokenize_fn = TEXT.preprocess

    def text2features(self, texts):
        if isinstance(texts, str): texts = [texts]
        tk_texts = [self.tokenize_fn(txt) for txt in texts]
        lens = np.array([len(txt) for txt in tk_texts])
        minl, maxl, meanl = lens.min(), lens.max(), lens.mean()
        print(f'Text length: min={minl}, max={maxl}, mean={meanl}')
        print(f'Padding all text to have fixed length = {self.max_pad}')

        xs = [[self.w2v.word2idx(word) for word in txt if self.w2v.word2idx(word) > 0] for txt in tk_texts]
        xs = pad_sequences(xs, maxlen=self.max_pad, padding='pre', truncating='post')
        xs = np.asarray(xs)
        return xs

    def __repr__(self):
        summary  = f'Tokenize function: {self.tokenize_fn.__name__} \n'
        summary += f'Max zero-padding len: {self.max_pad} \n'
        return summary

    def split_by_punctuation(self, s): return self.re_tok.sub(r' ', s).split()

    def split_by_popularity(self, word):
        unknown_pop_score = self.w2v.n_vocabs
        if self.w2v.word2idx(word) > 0:
            return self.w2v.word2idx(word), [word]

        if len(word)<=5:
            return unknown_pop_score, [word]

        if (word.replace('.','',1).replace(',','',1).isdigit()):
            return unknown_pop_score, [word] # Don't care about digit

        best_pop_score = unknown_pop_score
        best_split = None
        best_nsplit = 3 # Max split to split
        for i_cut in range(len(word)-2, 1, -1):
            prefix, core = word[:i_cut], word[i_cut:]

            if self.w2v.word2idx(core) < 0:
                continue
            core_score = self.w2v.word2idx(core)

            if self.w2v.word2idx(prefix) < 0:
                prefix_score, prefix = self.split_by_popularity(prefix)
            else:
                prefix_score, prefix = self.w2v.word2idx(prefix), [prefix]
            if (prefix_score >= unknown_pop_score): # Don't split if all splitted words are good
                continue
            pop_score = prefix_score + core_score
            words = prefix; words.append(core)

            if len(words) > best_nsplit: continue # Don't split more than best_nsplit
            if (len(words) < best_nsplit) or (pop_score < best_pop_score):
                #print(prefix, prefix_score, core_score, pop_score)
                best_split = words
                best_nsplit= len(words)
                best_pop_score = pop_score

        if best_split is None:
            return (unknown_pop_score, [word])
        else:
            return (best_pop_score, best_split)

    # TODO: Add new word to w2v with random vector if can't split well
    # TODO: Remove unpopular words from w2v, add special token for numbers or irrelevant words

    def tokenize(self, text):
        t = self.fastai_tok.spacy_tok(text)
        new_t = []
        for word in t:
            new_t.extend(self.split_by_punctuation(word))
        t = new_t
        new_t = []
        for word in t:
            word = word.lower()
            pop_score, words = self.split_by_popularity(word)
            new_t.extend(words)
            #if self.w2v.word2idx(word) < 0:
            #    print(words)
        return new_t


import pytest
from pytest import approx
from typeguard import typechecked
import numpy as np
import pandas as pd

from encomp.units import Quantity, Q, wraps, check, DimensionalityError
from encomp.utypes import *

def test_Q():

    # test that Quantity objects can be constructed
    Q(1, 'dimensionless')
    Q(1, 'kg')
    Q(1, 'bar')
    Q(1, 'h')
    Q(1, 'newton')
    Q(1, 'cSt')

    # make sure that the alias Q behaves identically to Quantity
    assert Q(1) == Quantity(1)
    assert type(Q(1)) is type(Quantity(1))
    assert type(Q) is type(Quantity)

    # ensure that the inputs can be nested
    Q(Q(1, 'kg'))
    mass = Q(12, 'kg')
    Q(Q(Q(Q(mass))))
    Q(Q(Q(Q(mass), 'lbs')))
    Q(Q(Q(Q(mass), 'lbs')), 'stone')

    # no unit input defaults to dimensionless
    assert Q(12).check('')
    assert Q(1) == Q(100, '%')
    Q['Dimensionless'](21)
    assert isinstance(Q(21), Q['Dimensionless'])

    assert Q(1) == Q('1')
    assert Q(1) == Q('\n1\n')
    assert Q(1) == Q('1 dimensionless')

    # check type of "m"
    assert isinstance(Q(1, 'meter').m, int)
    assert isinstance(Q(2.3, 'meter').m, float)
    assert isinstance(Q([2, 3.4], 'meter').m, np.ndarray)
    assert isinstance(Q(np.array([2, 3.4]), 'meter').m, np.ndarray)

    # input Quantity as unit
    Q(1, Q(2, 'bar'))

    # input Quantity as val
    Q(Q(2, 'bar'), 'kPa')

    # input Quantity as both val and unit
    Q(Q(2, 'bar'), Q(3, 'kPa'))
    Q(Q(2, 'bar'), Q(3, 'mmHg'))

    # check that the dimensionality constraints work
    Q[Length](1, 'm')
    Q[Pressure](1, 'kPa')
    Q[Temperature](1, '°C')

    # the dimensionalities can also be specified as strings
    Q['Temperature'](1, '°C')

    P = Q(1, 'bar')
    # this Quantity must have the same dimensionality as P
    Q[P](2, 'kPa')

    with pytest.raises(DimensionalityError):
        Q[Temperature](1, 'kg')
        Q[Pressure](1, 'meter')
        Q[Mass](1, P)

    # in-place conversion
    # NOTE: don't use this for objects that are passed in by the user
    P3 = Q(1, 'bar')
    P3.ito('kPa')
    P3.ito(Q(123123, 'kPa'))

    assert P3.m == approx(100, rel=1e-12)

    # test conversions to np.ndarray with int/float dtypes
    a = Q([1, 2, 3], 'bar')
    a.ito('kPa')

    a = Q(np.array([1, 2, 3.0]), 'bar')
    a.ito('kPa')

    a = Q(np.array([1.0, 2.0, 3.0]), 'bar')
    a.ito('kPa')

    # conversion to new object
    P4 = Q(1, 'bar')
    P4_b = P4.to('kPa')
    P4_b = P4.to(Q(123123, 'kPa'))

    assert P4_b.m == approx(100, rel=1e-12)

    assert Q(1, 'bar') == Q(100, 'kPa') == Q('0.1 MPa') == Q('1e5', 'Pa')

    # check that nested Quantity objects can be used as input
    # only the first value is used as magnitude, the other Quantity
    # objects are only used to determine unit
    P2 = Q(Q(2, 'feet_water'), Q(321321, 'kPa')).to(Q(123123, 'feet_water'))

    # floating point math might make this off at the N:th decimal
    assert P2.m == approx(2, rel=1e-12)
    assert isinstance(P2, Q['Pressure'])

    with pytest.raises(Exception):

        # incorrect dimensionalities should raise Exception
        Q(Q(2, 'feet_water'), Q(321321, 'kg')).to(Q(123123, 'feet_water'))

    # the UnitsContainer objects can be used to construct new dimensionalities
    Q[Length * Length * Length / Temperature](1, 'm³/K')

    with pytest.raises(Exception):
        Q[Pressure / Area](1, 'bar/m')

    # percent or %
    Q(1.124124e-3, '').to('%').to('percent')
    Q(1.124124e-3).to('%').to('percent')

    # pd.Series is converted to np.ndarray
    vals = [2, 3, 4]
    s = pd.Series(vals, name='Pressure')
    arr = Q(s, 'bar').to('kPa').m
    assert isinstance(arr, np.ndarray)
    assert arr[0] == 200

    # np.ndarray magnitudes equality check
    assert (Q(s, 'bar') == Q(vals, 'bar').to('kPa')).all()

    # support a single string as input if the
    # magnitude and units are separated by one or more spaces
    assert Q('1 meter').check(Length)
    assert Q('1 meter per second').check(Velocity)
    assert (Q('1 m') ** 3).check(Volume)

def test_wraps():

    # @wraps(ret, args, strict=True|False) is a convenience
    # decorator for making the input/output of a function into Quantity
    # however, it does not enforce the return value

    @wraps('kg', ('m', 'kg'), strict=True)
    def func(a, b):

        # this is incorrect, cannot add 1 to a dimensional Quantity
        return a * b**2 + 1

    assert isinstance(func(Q(1, 'yd'), Q(20, 'lbs')), Q['Mass'])
    assert Q(1, 'bar').check(Pressure)

def test_check():

    assert not Q(1, 'kg').check('[energy]')
    assert Q(1, 'kg').check(Mass)
    assert not Q(1, 'kg').check(Energy)

    @check('[length]', '[mass]')
    def func(a, b):

        return a * b

    func(Q(1, 'yd'), Q(20, 'lbs'))

def test_typechecked():

    @typechecked
    def func_a(a: Quantity['Temperature']) -> Quantity['Pressure']:
        return Q(2, 'bar')

    assert func_a(Q(2, 'degC')) == Q(2, 'bar')

    with pytest.raises(TypeError):
        func_a(Q(2, 'meter'))

    @typechecked
    def func_b(a: Quantity) -> Quantity['Pressure']:
        return a

    assert func_b(Q(2, 'bar')) == Q(2, 'bar')
    assert func_b(Q(2, 'psi')) == Q(2, 'psi')
    assert func_b(Q(2, 'mmHg')) == Q(2, 'mmHg')

    with pytest.raises(TypeError):
        func_a(Q(2, 'meter'))


"""
.. module:: VisMPL
    :platform: Unix, Windows
    :synopsis: Matplotlib visualization component for NURBS-Python

.. moduleauthor:: Onur Rauf Bingol <orbingol@gmail.com>

"""

from geomdl import vis
import numpy as np
import matplotlib as mpl
import matplotlib.tri as mpltri
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
from matplotlib import animation

class VisConfig(vis.VisConfigAbstract):
    """ Configuration class for Matplotlib visualization module.

    This class is only required when you would like to change the visual defaults of the plots and the figure,
    such as hiding control points plot or legend.

    The ``VisMPL`` module has the following configuration variables:

    * ``ctrlpts`` (bool): Control points polygon/grid visibility. *Default: True*
    * ``evalpts`` (bool): Curve/surface points visibility. *Default: True*
    * ``bbox`` (bool): Bounding box visibility. *Default: False*
    * ``legend`` (bool): Figure legend visibility. *Default: True*
    * ``axes`` (bool): Axes and figure grid visibility. *Default: True*
    * ``labels`` (bool): Axis labels visibility. *Default: True*
    * ``trims`` (bool): Trim curves visibility. *Default: True*
    * ``axes_equal`` (bool): Enables or disables equal aspect ratio for the axes. *Default: True*
    * ``figure_size`` (list): Size of the figure in (x, y). *Default: [10, 8]*
    * ``figure_dpi`` (int): Resolution of the figure in DPI. *Default: 96*
    * ``trim_size`` (int): Size of the trim curves. *Default: 20*
    * ``alpha`` (float): Opacity of the evaluated points. *Default: 1.0*

    There is also a ``debug`` configuration variable which currently adds quiver plots to 2-dimensional curves to show
    their directions.

    The following example illustrates the usage of the configuration class.

    .. code-block:: python
        :linenos:

        # Create a curve (or a surface) instance
        curve = NURBS.Curve()

        # Skipping degree, knot vector and control points assignments

        # Create a visualization configuration instance with no legend, no axes and set the resolution to 120 dpi
        vis_config = VisMPL.VisConfig(legend=False, axes=False, figure_dpi=120)

        # Create a visualization method instance using the configuration above
        vis_obj = VisMPL.VisCurve2D(vis_config)

        # Set the visualization method of the curve object
        curve.vis = vis_obj

        # Plot the curve
        curve.render()

    Please refer to the **Examples Repository** for more details.
    """

    def __init__(self, **kwargs):
        super(VisConfig, self).__init__(**kwargs)
        self.dtype = np.float
        self.display_ctrlpts = kwargs.get('ctrlpts', True)
        self.display_evalpts = kwargs.get('evalpts', True)
        self.display_bbox = kwargs.get('bbox', False)
        self.display_legend = kwargs.get('legend', True)
        self.display_axes = kwargs.get('axes', True)
        self.display_labels = kwargs.get('labels', True)
        self.display_trims = kwargs.get('trims', True)
        self.axes_equal = kwargs.get('axes_equal', True)
        self.figure_size = kwargs.get('figure_size', [10, 8])
        self.figure_dpi = kwargs.get('figure_dpi', 96)
        self.trim_size = kwargs.get('trim_size', 20)
        self.alpha = kwargs.get('alpha', 1.0)
        self.figure_image_filename = "temp-figure.png"
        self.debug_mode = kwargs.get('debug', False)  # debugging mode for determining the trim directions

    @staticmethod
    def set_axes_equal(ax):
        """ Sets equal aspect ratio across the three axes of a 3D plot.

        Contributed by Xuefeng Zhao.

        :param ax: a Matplotlib axis, e.g., as output from plt.gca().
        """
        bounds = [ax.get_xlim3d(), ax.get_ylim3d(), ax.get_zlim3d()]
        ranges = [abs(bound[1] - bound[0]) for bound in bounds]
        centers = [np.mean(bound) for bound in bounds]
        radius = 0.5 * max(ranges)
        lower_limits = centers - radius
        upper_limits = centers + radius
        ax.set_xlim3d([lower_limits[0], upper_limits[0]])
        ax.set_ylim3d([lower_limits[1], upper_limits[1]])
        ax.set_zlim3d([lower_limits[2], upper_limits[2]])

    @staticmethod
    def save_figure_as(fig, filename):
        """ Saves the figure as a file.

        :param fig: a Matplotlib figure instance
        :param filename: file name to save
        """
        if filename is not None:
            fig.savefig(str(filename), bbox_inches='tight')

    def is_notebook(self):
        """ Detects if Jupyter notebook GUI toolkit is active

        return: True if the module is running inside a Jupyter notebook
        rtype: bool
        """
        return True if "nbAgg" == mpl.get_backend() else False

class VisCurve2D(vis.VisAbstract):
    """ Matplotlib visualization module for 2D curves """
    def __init__(self, config=VisConfig(), **kwargs):
        super(VisCurve2D, self).__init__(config, **kwargs)

    def render(self, **kwargs):
        """ Plots the 2D curve and the control points polygon. """
        # Calling parent function
        super(VisCurve2D, self).render(**kwargs)

        # Initialize variables
        legend_proxy = []
        legend_names = []

        # Draw control points polygon and the curve
        fig = plt.figure(figsize=self.vconf.figure_size, dpi=self.vconf.figure_dpi)
        ax = fig.gca()

        # Start plotting
        for plot in self._plots:
            pts = np.array(plot['ptsarr'], dtype=self.vconf.dtype)
            # Plot control points
            if plot['type'] == 'ctrlpts' and self.vconf.display_ctrlpts:
                cpplot, = plt.plot(pts[:, 0], pts[:, 1], color=plot['color'], linestyle='-.', marker='o')
                legend_proxy.append(cpplot)
                legend_names.append(plot['name'])

            # Plot evaluated points
            if plot['type'] == 'evalpts' and self.vconf.display_evalpts:
                curveplt, = plt.plot(pts[:, 0], pts[:, 1], color=plot['color'], linestyle='-', alpha=self.vconf.alpha)
                legend_proxy.append(curveplt)
                legend_names.append(plot['name'])
                # Debugging for curve directions
                if self.vconf.debug_mode:
                    plt.quiver(pts[0, 0], pts[0, 1], pts[-1, 0] - pts[0, 0], pts[-1, 1] - pts[0, 1],
                               color='k', angles='xy', scale_units='xy', scale=1, width=0.003)

            # Plot bounding box
            if plot['type'] == 'bbox' and self.vconf.display_bbox:
                bboxplt, = plt.plot(pts[:, 0], pts[:, 1], color=plot['color'], linestyle='--')
                legend_proxy.append(bboxplt)
                legend_names.append(plot['name'])

            # Plot extras
            if plot['type'] == 'extras':
                extrasplt, = plt.plot(pts[:, 0], pts[:, 1],
                                      color=plot['color'][0], linestyle='-', linewidth=plot['color'][1])
                legend_proxy.append(extrasplt)
                legend_names.append(plot['name'])

        # Add legend
        if self.vconf.display_legend:
            plt.legend(legend_proxy, legend_names)

        # Remove axes
        if not self.vconf.display_axes:
            plt.axis('off')

        # Set aspect ratio
        if self.vconf.axes_equal:
            ax.set_aspect('equal')

        # Axis labels
        if self.vconf.display_labels:
            ax.set_xlabel('x')
            ax.set_ylabel('y')

        # Process keyword arguments
        fig_filename = kwargs.get('fig_save_as', None)
        fig_display = kwargs.get('display_plot', True)

        # Check if running inside a Jupyter notebook
        if not self.vconf.is_notebook():
            # Display the plot or save the figure
            if fig_display:
                plt.show()
            else:
                fig_filename = self.vconf.figure_image_filename if fig_filename is None else fig_filename
                self.vconf.save_figure_as(fig, fig_filename)

class VisCurve3D(vis.VisAbstract):
    """ Matplotlib visualization module for 3D curves. """
    def __init__(self, config=VisConfig(), **kwargs):
        super(VisCurve3D, self).__init__(config, **kwargs)

    def render(self, **kwargs):
        """ Plots the 3D curve and the control points polygon. """
        # Calling parent function
        super(VisCurve3D, self).render(**kwargs)

        # Initialize variables
        legend_proxy = []
        legend_names = []

        # Draw control points polygon and the 3D curve
        fig = plt.figure(figsize=self.vconf.figure_size, dpi=self.vconf.figure_dpi)
        ax = Axes3D(fig)

        # Start plotting
        for plot in self._plots:
            pts = np.array(plot['ptsarr'], dtype=self.vconf.dtype)

            # Try not to fail if the input is 2D
            if pts.shape[1] == 2:
                pts = np.c_[pts, np.zeros(pts.shape[0])]

            # Plot control points
            if plot['type'] == 'ctrlpts' and self.vconf.display_ctrlpts:
                ax.plot(pts[:, 0], pts[:, 1], pts[:, 2], color=plot['color'], linestyle='-.', marker='o')
                plot_proxy = mpl.lines.Line2D([0], [0], linestyle='-.', color=plot['color'], marker='o')
                legend_proxy.append(plot_proxy)
                legend_names.append(plot['name'])

            # Plot evaluated points
            if plot['type'] == 'evalpts' and self.vconf.display_evalpts:
                ax.plot(pts[:, 0], pts[:, 1], pts[:, 2], color=plot['color'], linestyle='-', alpha=self.vconf.alpha)
                plot_proxy = mpl.lines.Line2D([0], [0], linestyle='-', color=plot['color'])
                legend_proxy.append(plot_proxy)
                legend_names.append(plot['name'])

            # Plot bounding box
            if plot['type'] == 'bbox' and self.vconf.display_bbox:
                ax.plot(pts[:, 0], pts[:, 1], pts[:, 2], color=plot['color'], linestyle='--')
                plot_proxy = mpl.lines.Line2D([0], [0], linestyle='--', color=plot['color'])
                legend_proxy.append(plot_proxy)
                legend_names.append(plot['name'])

            # Plot extras
            if plot['type'] == 'extras':
                ax.plot(pts[:, 0], pts[:, 1], pts[:, 2],
                        color=plot['color'][0], linestyle='-', linewidth=plot['color'][1])
                plot_proxy = mpl.lines.Line2D([0], [0], linestyle='-', color=plot['color'][0])
                legend_proxy.append(plot_proxy)
                legend_names.append(plot['name'])

        # Add legend to 3D plot, @ref: https://stackoverflow.com/a/20505720
        if self.vconf.display_legend:
            ax.legend(legend_proxy, legend_names, numpoints=1)

        # Remove axes
        if not self.vconf.display_axes:
            plt.axis('off')

        # Set axes equal
        if self.vconf.axes_equal:
            self.vconf.set_axes_equal(ax)

        # Axis labels
        if self.vconf.display_labels:
            ax.set_xlabel('x')
            ax.set_ylabel('y')
            ax.set_zlabel('z')

        # Process keyword arguments
        fig_filename = kwargs.get('fig_save_as', None)
        fig_display = kwargs.get('display_plot', True)

        # Check if running inside a Jupyter notebook
        if not self.vconf.is_notebook():
            # Display the plot or save the figure
            if fig_display:
                plt.show()
            else:
                fig_filename = self.vconf.figure_image_filename if fig_filename is None else fig_filename
                self.vconf.save_figure_as(fig, fig_filename)

class VisSurface(vis.VisAbstract):
    """ Matplotlib visualization module for surfaces.

    Wireframe plot for the control points and triangulated plot (using ``plot_trisurf``) for the surface points.
    The surface is triangulated externally using :py:func:`.utilities.make_triangle_mesh()` function.
    """
    def __init__(self, config=VisConfig(), **kwargs):
        super(VisSurface, self).__init__(config, **kwargs)
        self._module_config['ctrlpts'] = "quads"
        self._module_config['evalpts'] = "triangles"

    def animate(self, **kwargs):
        """ Animates the surface.

        This function only animates the triangulated surface. There will be no other elements, such as control points
        grid or bounding box.

        Keyword arguments:
            * ``colormap``: applies colormap to the surface

        Colormaps are a visualization feature of Matplotlib. They can be used for several types of surface plots via
        the following import statement: ``from matplotlib import cm``

        The following link displays the list of Matplolib colormaps and some examples on colormaps:
        https://matplotlib.org/tutorials/colors/colormaps.html
        """
        # Calling parent render function
        super(VisSurface, self).render(**kwargs)

        # Colormaps
        surf_cmaps = kwargs.get('colormap', None)

        # Initialize variables
        tri_idxs = []
        vert_coords = []
        trisurf_params = []
        frames = []
        frames_tris = []
        num_vertices = 0

        # Start plotting of the surface and the control points grid
        fig = plt.figure(figsize=self.vconf.figure_size, dpi=self.vconf.figure_dpi)
        ax = Axes3D(fig)

        # Start plotting
        surf_count = 0
        for plot in self._plots:
            # Plot evaluated points
            if plot['type'] == 'evalpts' and self.vconf.display_evalpts:
                # Use internal triangulation algorithm instead of Qhull (MPL default)
                verts = plot['ptsarr'][0]
                tris = plot['ptsarr'][1]
                # Extract zero-indexed vertex number list
                tri_idxs += [[ti + num_vertices for ti in tri.data] for tri in tris]
                # Extract vertex coordinates
                vert_coords += [vert.data for vert in verts]
                # Update number of vertices
                num_vertices = len(vert_coords)

                # Determine the color or the colormap of the triangulated plot
                params = {}
                if surf_cmaps:
                    try:
                        params['cmap'] = surf_cmaps[surf_count]
                        surf_count += 1
                    except IndexError:
                        params['color'] = plot['color']
                else:
                    params['color'] = plot['color']
                trisurf_params += [params for _ in range(len(tris))]

        # Pre-processing for the animation
        pts = np.array(vert_coords, dtype=self.vconf.dtype)

        # Create the frames (Artists)
        for tidx, pidx in zip(tri_idxs, trisurf_params):
            frames_tris.append(tidx)
            # Create MPL Triangulation object
            triangulation = mpltri.Triangulation(pts[:, 0], pts[:, 1], triangles=frames_tris)
            # Use custom Triangulation object and the choice of color/colormap to plot the surface
            p3df = ax.plot_trisurf(triangulation, pts[:, 2], alpha=self.vconf.alpha, **pidx)
            # Add to frames list
            frames.append([p3df])

        # Create MPL ArtistAnimation
        ani = animation.ArtistAnimation(fig, frames, interval=100, blit=True, repeat_delay=1000)

        # Remove axes
        if not self.vconf.display_axes:
            plt.axis('off')

        # Set axes equal
        if self.vconf.axes_equal:
            self.vconf.set_axes_equal(ax)

        # Axis labels
        if self.vconf.display_labels:
            ax.set_xlabel('x')
            ax.set_ylabel('y')
            ax.set_zlabel('z')

        # Process keyword arguments
        fig_filename = kwargs.get('fig_save_as', None)
        fig_display = kwargs.get('display_plot', True)

        # Check if running inside a Jupyter notebook
        if not self.vconf.is_notebook():
            # Display the plot or save the figure
            if fig_display:
                plt.show()
            else:
                fig_filename = self.vconf.figure_image_filename if fig_filename is None else fig_filename
                self.vconf.save_figure_as(fig, fig_filename)

    def render(self, **kwargs):
        """ Plots the surface and the control points grid.

        Keyword arguments:
            * ``colormap``: applies colormap to the surface

        Colormaps are a visualization feature of Matplotlib. They can be used for several types of surface plots via
        the following import statement: ``from matplotlib import cm``

        The following link displays the list of Matplolib colormaps and some examples on colormaps:
        https://matplotlib.org/tutorials/colors/colormaps.html
        """
        # Calling parent function
        super(VisSurface, self).render(**kwargs)

        # Colormaps
        surf_cmaps = kwargs.get('colormap', None)

        # Initialize variables
        legend_proxy = []
        legend_names = []

        # Start plotting of the surface and the control points grid
        fig = plt.figure(figsize=self.vconf.figure_size, dpi=self.vconf.figure_dpi)
        ax = Axes3D(fig)

        surf_count = 0
        # Start plotting
        for plot in self._plots:
            # Plot control points
            if plot['type'] == 'ctrlpts' and self.vconf.display_ctrlpts:
                vertices = [v.data for v in plot['ptsarr'][0]]
                faces = [q.data for q in plot['ptsarr'][1]]
                for q in faces:
                    el = np.array([vertices[i] for i in q], dtype=self.vconf.dtype)
                    el[:, 2] += self._ctrlpts_offset
                    pc3d = Poly3DCollection([el], alpha=0.0, edgecolors=plot['color'], linewidths=1.0, linestyles='-.')
                    pc3d.set_facecolor(None)
                    ax.add_collection3d(pc3d)
                pts = np.array(vertices, dtype=self.vconf.dtype)
                pts[:, 2] += self._ctrlpts_offset
                ax.scatter(pts[:, 0], pts[:, 1], pts[:, 2], color=plot['color'], linestyle='-.', marker='o')
                plot_proxy = mpl.lines.Line2D([0], [0], linestyle='-.', color=plot['color'], marker='o')
                legend_proxy.append(plot_proxy)
                legend_names.append(plot['name'])

            # Plot evaluated points
            if plot['type'] == 'evalpts' and self.vconf.display_evalpts:
                # Use internal triangulation algorithm instead of Qhull (MPL default)
                verts = plot['ptsarr'][0]
                tris = plot['ptsarr'][1]
                # Extract zero-indexed vertex number list
                tri_idxs = [tri.data for tri in tris]
                # Extract vertex coordinates
                vert_coords = [vert.data for vert in verts]
                pts = np.array(vert_coords, dtype=self.vconf.dtype)

                # Determine the color or the colormap of the triangulated plot
                trisurf_params = {}
                if surf_cmaps:
                    try:
                        trisurf_params['cmap'] = surf_cmaps[surf_count]
                        surf_count += 1
                    except IndexError:
                        trisurf_params['color'] = plot['color']
                else:
                    trisurf_params['color'] = plot['color']

                # Create MPL Triangulation object
                if pts.size != 0:
                    triangulation = mpltri.Triangulation(pts[:, 0], pts[:, 1], triangles=tri_idxs)
                    # Use custom Triangulation object and the choice of color/colormap to plot the surface
                    ax.plot_trisurf(triangulation, pts[:, 2], alpha=self.vconf.alpha, **trisurf_params)
                    # Add to legend
                    plot_proxy = mpl.lines.Line2D([0], [0], linestyle='none', color=plot['color'], marker='^')
                    legend_proxy.append(plot_proxy)
                    legend_names.append(plot['name'])

            # Plot bounding box
            if plot['type'] == 'bbox' and self.vconf.display_bbox:
                pts = np.array(plot['ptsarr'], dtype=self.vconf.dtype)
                ax.plot(pts[:, 0], pts[:, 1], pts[:, 2], color=plot['color'], linestyle='--')
                plot_proxy = mpl.lines.Line2D([0], [0], linestyle='--', color=plot['color'])
                legend_proxy.append(plot_proxy)
                legend_names.append(plot['name'])

            # Plot trim curves
            if self.vconf.display_trims:
                if plot['type'] == 'trimcurve':
                    pts = np.array(plot['ptsarr'], dtype=self.vconf.dtype)
                    ax.scatter(pts[:, 0], pts[:, 1], pts[:, 2], color=plot['color'], marker='o',
                               s=self.vconf.trim_size, depthshade=False)
                    plot_proxy = mpl.lines.Line2D([0], [0], linestyle='none', color=plot['color'], marker='o')
                    legend_proxy.append(plot_proxy)
                    legend_names.append(plot['name'])

            # Plot extras
            if plot['type'] == 'extras':
                pts = np.array(plot['ptsarr'], dtype=self.vconf.dtype)
                ax.plot(pts[:, 0], pts[:, 1], pts[:, 2],
                        color=plot['color'][0], linestyle='-', linewidth=plot['color'][1])
                plot_proxy = mpl.lines.Line2D([0], [0], linestyle='-', color=plot['color'][0])
                legend_proxy.append(plot_proxy)
                legend_names.append(plot['name'])

        # Add legend to 3D plot, @ref: https://stackoverflow.com/a/20505720
        if self.vconf.display_legend:
            ax.legend(legend_proxy, legend_names, numpoints=1)

        # Remove axes
        if not self.vconf.display_axes:
            plt.axis('off')

        # Set axes equal
        if self.vconf.axes_equal:
            self.vconf.set_axes_equal(ax)

        # Axis labels
        if self.vconf.display_labels:
            ax.set_xlabel('x')
            ax.set_ylabel('y')
            ax.set_zlabel('z')

        # Process keyword arguments
        fig_filename = kwargs.get('fig_save_as', None)
        fig_display = kwargs.get('display_plot', True)

        # Check if running inside a Jupyter notebook
        if not self.vconf.is_notebook():
            # Display the plot or save the figure
            if fig_display:
                plt.show()
            else:
                fig_filename = self.vconf.figure_image_filename if fig_filename is None else fig_filename
                self.vconf.save_figure_as(fig, fig_filename)

class VisSurfWireframe(vis.VisAbstract):
    """ Matplotlib visualization module for surfaces.

    Scatter plot for the control points and wireframe plot for the surface points.
    """
    def __init__(self, config=VisConfig(), **kwargs):
        super(VisSurfWireframe, self).__init__(config, **kwargs)
        self._module_config['ctrlpts'] = "points"
        self._module_config['evalpts'] = "quads"

    def render(self, **kwargs):
        """ Plots the surface and the control points grid. """
        # Calling parent function
        super(VisSurfWireframe, self).render(**kwargs)

        # Initialize variables
        legend_proxy = []
        legend_names = []

        # Start plotting of the surface and the control points grid
        fig = plt.figure(figsize=self.vconf.figure_size, dpi=self.vconf.figure_dpi)
        ax = Axes3D(fig)

        # Start plotting
        for plot in self._plots:
            # Plot control points
            if plot['type'] == 'ctrlpts' and self.vconf.display_ctrlpts:
                pts = np.array(plot['ptsarr'], dtype=self.vconf.dtype)
                cp_z = pts[:, 2] + self._ctrlpts_offset
                ax.scatter(pts[:, 0], pts[:, 1], cp_z, color=plot['color'], s=25, depthshade=True)
                plot_proxy = mpl.lines.Line2D([0], [0], linestyle='-.', color=plot['color'], marker='o')
                legend_proxy.append(plot_proxy)
                legend_names.append(plot['name'])

            # Plot evaluated points
            if plot['type'] == 'evalpts' and self.vconf.display_evalpts:
                vertices = [v.data for v in plot['ptsarr'][0]]
                faces = [q.data for q in plot['ptsarr'][1]]
                for q in faces:
                    el = np.array([vertices[i] for i in q], dtype=self.vconf.dtype)
                    el[:, 2] += self._ctrlpts_offset
                    pc3d = Poly3DCollection([el], alpha=0.0, edgecolors=plot['color'], linewidths=0.5, linestyles='-')
                    pc3d.set_facecolor(None)
                    ax.add_collection3d(pc3d)
                pts = np.array(vertices, dtype=self.vconf.dtype)
                ax.scatter(pts[:, 0], pts[:, 1], pts[:, 2], color=plot['color'], s=5)
                plot_proxy = mpl.lines.Line2D([0], [0], linestyle='-', color=plot['color'])
                legend_proxy.append(plot_proxy)
                legend_names.append(plot['name'])

            # Plot bounding box
            if plot['type'] == 'bbox' and self.vconf.display_bbox:
                pts = np.array(plot['ptsarr'], dtype=self.vconf.dtype)
                ax.plot(pts[:, 0], pts[:, 1], pts[:, 2], color=plot['color'], linestyle='--')
                plot_proxy = mpl.lines.Line2D([0], [0], linestyle='--', color=plot['color'])
                legend_proxy.append(plot_proxy)
                legend_names.append(plot['name'])

            # Plot trim curves
            if self.vconf.display_trims:
                if plot['type'] == 'trimcurve':
                    pts = np.array(plot['ptsarr'], dtype=self.vconf.dtype)
                    ax.scatter(pts[:, 0], pts[:, 1], pts[:, 2], color=plot['color'], marker='o',
                               s=self.vconf.trim_size, depthshade=False)
                    plot_proxy = mpl.lines.Line2D([0], [0], linestyle='none', color=plot['color'], marker='o')
                    legend_proxy.append(plot_proxy)
                    legend_names.append(plot['name'])

            # Plot extras
            if plot['type'] == 'extras':
                pts = np.array(plot['ptsarr'], dtype=self.vconf.dtype)
                ax.plot(pts[:, 0], pts[:, 1], pts[:, 2],
                        color=plot['color'][0], linestyle='-', linewidth=plot['color'][1])
                plot_proxy = mpl.lines.Line2D([0], [0], linestyle='-', color=plot['color'][0])
                legend_proxy.append(plot_proxy)
                legend_names.append(plot['name'])

        # Add legend to 3D plot, @ref: https://stackoverflow.com/a/20505720
        if self.vconf.display_legend:
            ax.legend(legend_proxy, legend_names, numpoints=1)

        # Remove axes
        if not self.vconf.display_axes:
            plt.axis('off')

        # Set axes equal
        if self.vconf.axes_equal:
            self.vconf.set_axes_equal(ax)

        # Axis labels
        if self.vconf.display_labels:
            ax.set_xlabel('x')
            ax.set_ylabel('y')
            ax.set_zlabel('z')

        # Process keyword arguments
        fig_filename = kwargs.get('fig_save_as', None)
        fig_display = kwargs.get('display_plot', True)

        # Check if running inside a Jupyter notebook
        if not self.vconf.is_notebook():
            # Display the plot or save the figure
            if fig_display:
                plt.show()
            else:
                fig_filename = self.vconf.figure_image_filename if fig_filename is None else fig_filename
                self.vconf.save_figure_as(fig, fig_filename)

class VisSurfScatter(vis.VisAbstract):
    """ Matplotlib visualization module for surfaces.

    Wireframe plot for the control points and scatter plot for the surface points.
    """
    def __init__(self, config=VisConfig(), **kwargs):
        super(VisSurfScatter, self).__init__(config, **kwargs)
        self._module_config['ctrlpts'] = "quads"
        self._module_config['evalpts'] = "points"

    def render(self, **kwargs):
        """ Plots the surface and the control points grid. """
        # Calling parent function
        super(VisSurfScatter, self).render(**kwargs)

        # Initialize variables
        legend_proxy = []
        legend_names = []

        # Start plotting of the surface and the control points grid
        fig = plt.figure(figsize=self.vconf.figure_size, dpi=self.vconf.figure_dpi)
        ax = Axes3D(fig)

        # Start plotting
        for plot in self._plots:
            # Plot control points
            if plot['type'] == 'ctrlpts' and self.vconf.display_ctrlpts:
                vertices = [v.data for v in plot['ptsarr'][0]]
                faces = [q.data for q in plot['ptsarr'][1]]
                for q in faces:
                    el = np.array([vertices[i] for i in q], dtype=self.vconf.dtype)
                    el[:, 2] += self._ctrlpts_offset
                    pc3d = Poly3DCollection([el], alpha=0.0, edgecolors=plot['color'], linewidths=1.0, linestyles='-.')
                    pc3d.set_facecolor(None)
                    ax.add_collection3d(pc3d)
                pts = np.array(vertices, dtype=self.vconf.dtype)
                pts[:, 2] += self._ctrlpts_offset
                ax.scatter(pts[:, 0], pts[:, 1], pts[:, 2], color=plot['color'], linestyle='-.', marker='o')
                plot_proxy = mpl.lines.Line2D([0], [0], linestyle='-.', color=plot['color'], marker='o')
                legend_proxy.append(plot_proxy)
                legend_names.append(plot['name'])

            # Plot evaluated points
            if plot['type'] == 'evalpts' and self.vconf.display_evalpts:
                pts = np.array(plot['ptsarr'], dtype=self.vconf.dtype)
                ax.scatter(pts[:, 0], pts[:, 1], pts[:, 2],
                           color=plot['color'], s=50, depthshade=True, alpha=self.vconf.alpha)
                plot_proxy = mpl.lines.Line2D([0], [0], linestyle='none', color=plot['color'], marker='o')
                legend_proxy.append(plot_proxy)
                legend_names.append(plot['name'])

            # Plot bounding box
            if plot['type'] == 'bbox' and self.vconf.display_bbox:
                pts = np.array(plot['ptsarr'], dtype=self.vconf.dtype)
                ax.plot(pts[:, 0], pts[:, 1], pts[:, 2], color=plot['color'], linestyle='--')
                plot_proxy = mpl.lines.Line2D([0], [0], linestyle='--', color=plot['color'])
                legend_proxy.append(plot_proxy)
                legend_names.append(plot['name'])

            # Plot trim curves
            if self.vconf.display_trims:
                if plot['type'] == 'trimcurve':
                    pts = np.array(plot['ptsarr'], dtype=self.vconf.dtype)
                    ax.scatter(pts[:, 0], pts[:, 1], pts[:, 2], color=plot['color'], marker='o',
                               s=self.vconf.trim_size, depthshade=False)
                    plot_proxy = mpl.lines.Line2D([0], [0], linestyle='none', color=plot['color'], marker='o')
                    legend_proxy.append(plot_proxy)
                    legend_names.append(plot['name'])

            # Plot extras
            if plot['type'] == 'extras':
                pts = np.array(plot['ptsarr'], dtype=self.vconf.dtype)
                ax.plot(pts[:, 0], pts[:, 1], pts[:, 2],
                        color=plot['color'][0], linestyle='-', linewidth=plot['color'][1])
                plot_proxy = mpl.lines.Line2D([0], [0], linestyle='-', color=plot['color'][0])
                legend_proxy.append(plot_proxy)
                legend_names.append(plot['name'])

        # Add legend to 3D plot, @ref: https://stackoverflow.com/a/20505720
        if self.vconf.display_legend:
            ax.legend(legend_proxy, legend_names, numpoints=1)

        # Remove axes
        if not self.vconf.display_axes:
            plt.axis('off')

        # Set axes equal
        if self.vconf.axes_equal:
            self.vconf.set_axes_equal(ax)

        # Axis labels
        if self.vconf.display_labels:
            ax.set_xlabel('x')
            ax.set_ylabel('y')
            ax.set_zlabel('z')

        # Process keyword arguments
        fig_filename = kwargs.get('fig_save_as', None)
        fig_display = kwargs.get('display_plot', True)

        # Check if running inside a Jupyter notebook
        if not self.vconf.is_notebook():
            # Display the plot or save the figure
            if fig_display:
                plt.show()
            else:
                fig_filename = self.vconf.figure_image_filename if fig_filename is None else fig_filename
                self.vconf.save_figure_as(fig, fig_filename)

class VisVolume(vis.VisAbstract):
    """ Matplotlib visualization module for volumes. """
    def __init__(self, config=VisConfig(), **kwargs):
        super(VisVolume, self).__init__(config, **kwargs)
        self._module_config['ctrlpts'] = "points"
        self._module_config['evalpts'] = "points"

    def render(self, **kwargs):
        """ Plots the volume and the control points. """
        # Calling parent function
        super(VisVolume, self).render(**kwargs)

        # Initialize variables
        legend_proxy = []
        legend_names = []

        # Start plotting of the surface and the control points grid
        fig = plt.figure(figsize=self.vconf.figure_size, dpi=self.vconf.figure_dpi)
        ax = Axes3D(fig)

        # Start plotting
        for plot in self._plots:
            pts = np.array(plot['ptsarr'], dtype=self.vconf.dtype)
            # Plot control points
            if plot['type'] == 'ctrlpts' and self.vconf.display_ctrlpts:
                ax.scatter(pts[:, 0], pts[:, 1], pts[:, 2], color=plot['color'], marker='^', s=20, depthshade=True)
                plot_proxy = mpl.lines.Line2D([0], [0], linestyle='none', color=plot['color'], marker='^')
                legend_proxy.append(plot_proxy)
                legend_names.append(plot['name'])

            # Plot evaluated points
            if plot['type'] == 'evalpts' and self.vconf.display_evalpts:
                ax.scatter(pts[:, 0], pts[:, 1], pts[:, 2],
                           color=plot['color'], marker='o', s=10, depthshade=True, alpha=self.vconf.alpha)
                plot_proxy = mpl.lines.Line2D([0], [0], linestyle='none', color=plot['color'], marker='o')
                legend_proxy.append(plot_proxy)
                legend_names.append(plot['name'])

            # Plot bounding box
            if plot['type'] == 'bbox' and self.vconf.display_bbox:
                ax.plot(pts[:, 0], pts[:, 1], pts[:, 2], color=plot['color'], linestyle='--')
                plot_proxy = mpl.lines.Line2D([0], [0], linestyle='--', color=plot['color'])
                legend_proxy.append(plot_proxy)
                legend_names.append(plot['name'])

            # Plot extras
            if plot['type'] == 'extras':
                ax.plot(pts[:, 0], pts[:, 1], pts[:, 2],
                        color=plot['color'][0], linestyle='-', linewidth=plot['color'][1])
                plot_proxy = mpl.lines.Line2D([0], [0], linestyle='-', color=plot['color'][0])
                legend_proxy.append(plot_proxy)
                legend_names.append(plot['name'])

        # Add legend to 3D plot, @ref: https://stackoverflow.com/a/20505720
        if self.vconf.display_legend:
            ax.legend(legend_proxy, legend_names, numpoints=1)

        # Remove axes
        if not self.vconf.display_axes:
            plt.axis('off')

        # Set axes equal
        if self.vconf.axes_equal:
            self.vconf.set_axes_equal(ax)

        # Axis labels
        if self.vconf.display_labels:
            ax.set_xlabel('x')
            ax.set_ylabel('y')
            ax.set_zlabel('z')

        # Process keyword arguments
        fig_filename = kwargs.get('fig_save_as', None)
        fig_display = kwargs.get('display_plot', True)

        # Check if running inside a Jupyter notebook
        if not self.vconf.is_notebook():
            # Display the plot or save the figure
            if fig_display:
                plt.show()
            else:
                fig_filename = self.vconf.figure_image_filename if fig_filename is None else fig_filename
                self.vconf.save_figure_as(fig, fig_filename)

class VisVoxel(vis.VisAbstract):
    """ Matplotlib visualization module for voxel representation of the volumes. """
    def __init__(self, config=VisConfig(), **kwargs):
        super(VisVoxel, self).__init__(config, **kwargs)
        self._module_config['ctrlpts'] = "points"
        self._module_config['evalpts'] = "voxels"

    def render(self, **kwargs):
        """ Displays the voxels and the control points. """
        # Calling parent function
        super(VisVoxel, self).render(**kwargs)

        # Initialize variables
        legend_proxy = []
        legend_names = []

        # Start plotting of the surface and the control points grid
        fig = plt.figure(figsize=self.vconf.figure_size, dpi=self.vconf.figure_dpi)
        ax = Axes3D(fig)

        # Start plotting
        for plot in self._plots:
            # Plot control points
            if plot['type'] == 'ctrlpts' and self.vconf.display_ctrlpts:
                pts = np.array(plot['ptsarr'], dtype=self.vconf.dtype)
                ax.scatter(pts[:, 0], pts[:, 1], pts[:, 2], color=plot['color'], marker='^', s=20, depthshade=True)
                plot_proxy = mpl.lines.Line2D([0], [0], linestyle='none', color=plot['color'], marker='^')
                legend_proxy.append(plot_proxy)
                legend_names.append(plot['name'])

            # Plot evaluated points
            if plot['type'] == 'evalpts' and self.vconf.display_evalpts:
                faces = np.array(plot['ptsarr'][1], dtype=self.vconf.dtype)
                filled = np.array(plot['ptsarr'][2], dtype=self.vconf.dtype)
                # Find filled voxels
                faces_filled = np.concatenate(faces[filled == 1.0])
                # Create a single Poly3DCollection object
                pc3d = Poly3DCollection(faces_filled, facecolors=plot['color'], edgecolors='k')
                ax.add_collection3d(pc3d)
                # Set axis limits
                gf_min = np.amin(faces_filled, axis=(0, 1))
                gf_max = np.amax(faces_filled, axis=(0, 1))
                ax.set_xlim([gf_min[0], gf_max[0]])
                ax.set_ylim([gf_min[1], gf_max[1]])
                ax.set_zlim([gf_min[2], gf_max[2]])
                # Legend
                plot_proxy = mpl.lines.Line2D([0], [0], linestyle='none', color=plot['color'], marker='o')
                legend_proxy.append(plot_proxy)
                legend_names.append(plot['name'])

            # Plot bounding box
            if plot['type'] == 'bbox' and self.vconf.display_bbox:
                ax.plot(pts[:, 0], pts[:, 1], pts[:, 2], color=plot['color'], linestyle='--')
                plot_proxy = mpl.lines.Line2D([0], [0], linestyle='--', color=plot['color'])
                legend_proxy.append(plot_proxy)
                legend_names.append(plot['name'])

            # Plot extras
            if plot['type'] == 'extras':
                ax.plot(pts[:, 0], pts[:, 1], pts[:, 2],
                        color=plot['color'][0], linestyle='-', linewidth=plot['color'][1])
                plot_proxy = mpl.lines.Line2D([0], [0], linestyle='-', color=plot['color'][0])
                legend_proxy.append(plot_proxy)
                legend_names.append(plot['name'])

        # Add legend to 3D plot, @ref: https://stackoverflow.com/a/20505720
        if self.vconf.display_legend:
            ax.legend(legend_proxy, legend_names, numpoints=1)

        # Remove axes
        if not self.vconf.display_axes:
            plt.axis('off')

        # Set axes equal
        if self.vconf.axes_equal:
            self.vconf.set_axes_equal(ax)

        # Axis labels
        if self.vconf.display_labels:
            ax.set_xlabel('x')
            ax.set_ylabel('y')
            ax.set_zlabel('z')

        # Process keyword arguments
        fig_filename = kwargs.get('fig_save_as', None)
        fig_display = kwargs.get('display_plot', True)

        # Check if running inside a Jupyter notebook
        if not self.vconf.is_notebook():
            # Display the plot or save the figure
            if fig_display:
                plt.show()
            else:
                fig_filename = self.vconf.figure_image_filename if fig_filename is None else fig_filename
                self.vconf.save_figure_as(fig, fig_filename)


# -*- coding: utf-8 -*-
"""
Created on Sun May 10 09:59:15 2020

@author: prophet lin
"""

import os
import pandas as pd
import numpy as np
out = open("./res.txt", 'w+')

#%% 读取生成式  @表示为空
generator = {}
start = None    #开始符号
ter_set = set() #终结符集合
non_set = set() #非终结符集合
all_set = set()
#grammar2
f = open('./tiny.txt')
for line in f:
    line = line.split('->')
    #添加开始符号
    if not non_set:
        start = line[0].split()[0]
    #非终结符
    non_sym = line[0].split()[0]
    non_set.add(non_sym)
    #右侧所有符号
    all_syms = line[1].split() #[:-1] delete '/n'
    all_set = all_set.union(set(all_syms))

    if non_sym not in generator:
        generator[non_sym] = []
    generator[non_sym].append(all_syms)
#求非终结符集
ter_set = all_set - non_set
print("起始符号：{0}\n非终结符：{1}\n终结符：{2}\n===========".format(start, non_set, ter_set), file=out)
#%% 消除左递归

#%% 提取左因子

#%% 生成First集
first = {}
#第一轮 把所有开头的非终结符和空放入First集
for l_sym in generator:
    for each in generator[l_sym]:
        if l_sym not in first:
            first[l_sym] = set()
        if each[0] in ter_set:
            first[l_sym].add(each[0])
#循环轮 添加非终结符，循环直到不再变化
update = 1
while update == 1:
    update = 0
    for l_sym in generator:
        for each in generator[l_sym]:
            #处理空符号，尤其是前面所有的均为空
            emputy_flag = 0
            for i in range(len(each)):
                if each[i] in non_set:
                    #如果添加导致改变，设置标志位
                    temp = first[l_sym].union( first[each[i]] - set('@') )
                    if first[l_sym] != temp:
                        update = 1
                        first[l_sym] = temp
                if each[i] in ter_set and each[i] not in first[l_sym]:
                    update = 1
                    first[l_sym].add(each[i])
                if each[i] in ter_set or (each[i] in non_set and '@' not in first[each[i]]):
                    emputy_flag = 1
                    break

            if emputy_flag == 0:
                first[l_sym].add('@')

print("First集：", file=out)
for item in first:
    print("{0}:{1}".format(item,first[item]), file=out)
print("===========", file=out)
#%% 生成Follow集
# $ 加入起始符号
follow = {}
for non_sym in non_set:
    follow[non_sym] = set()
follow[start] = set('$')

# 第一轮 将非终结符后的终结符添加到FOLLOW中
for l_sym in generator:
    for each in generator[l_sym]:
        for i in range(len(each)-1):
            if each[i] in non_set and each[i+1] in ter_set:
                follow[each[i]].add(each[i+1])
#第二轮 集合间的同步，非终结符后的非终结符first集，左侧添加到右侧最后，循环直到不再变化
update = 1
while update == 1:
    update = 0
    for l_sym in generator:
        for each in generator[l_sym]:
            # 产生式后加入$,用来合并逻辑,不必从末尾向前找空，而是顺序向后
            temp_produce = each.copy()
            temp_produce.append('$')
            for i in range(len(each)):
                next_p = i
                while True:
                    next_p += 1
                    #非终结符后的非终结符first集
                    if temp_produce[i] in non_set and temp_produce[next_p] in non_set:
                        temp = follow[temp_produce[i]].union( first[temp_produce[next_p]] - set('@') )
                        if follow[temp_produce[i]] != temp:
                            update = 1
                            follow[temp_produce[i]] = temp
                    #左侧follow添加到右侧最后
                    if temp_produce[i] in non_set and temp_produce[next_p] == '$':
                        temp = follow[temp_produce[i]].union( follow[l_sym] )
                        if follow[temp_produce[i]] != temp:
                            update = 1
                            follow[temp_produce[i]] = temp
                    if temp_produce[next_p] in ter_set or temp_produce[next_p] == '$' or ( '@' not in first[temp_produce[next_p]]):
                        break
print("Follow集：", file=out)
for item in follow:
    print("{0}:{1}".format(item,follow[item]), file=out)
print("===========", file=out)
#%% 生成LL1分析表 判断是否是LL1文法
vaild = True
ter_list = list(ter_set)
non_list = list(non_set)
ter_list.append('$')
ter_list.remove('@')
ll1_table = pd.DataFrame(columns = ter_list,index = non_list)
generator_list = []
count = 0
for l_sym in generator:
    for each in generator[l_sym]:
        #print(each,count)
        generator_list.append([l_sym,each])
        if each[0] == '@':
            #添加Follow
            for sym in follow[l_sym]:
                if np.isnan(ll1_table.loc[l_sym,sym]):
                    ll1_table.loc[l_sym,sym] = count
                else:
                    vaild = False
            count += 1
            continue
        if each[0] in ter_set:
            if np.isnan(ll1_table.loc[l_sym,each[0]]):
                ll1_table.loc[l_sym,each[0]] = count
            else:
                vaild = False
        #TODO:处理first中有空的情况
        if each[0] in non_set:
            for sym in first[each[0]]:
                if np.isnan(ll1_table.loc[l_sym,sym]):
                    ll1_table.loc[l_sym,sym] = count
                else:
                    vaild = False
        count += 1

if not vaild:
    print('文法不满足LL（1）条件！')
ll1_table.to_excel('LL1.xlsx')
#%% 分析栈分析代码 以及构造抽象语法树
token_file = open('./token_list.txt')
tokens = []
for line in token_file:
    tokens.append(line[:-1])
tokens.append('$')
stack = []
stack.append("$")
stack.append(start)
while stack:
    print('=====================\n','stack:',stack,'\n tokens:',tokens, file=out)

    #match
    if stack[-1] in ter_set or (stack[-1] == '$' and tokens[-1] == '$'):
        if stack[-1] == tokens[0]:
            stack.pop()
            tokens.pop(0)
            continue
        else:
            print('不满足文法')
            break
    #action
    if stack[-1] in non_set:
        if np.isnan(ll1_table.loc[stack[-1],tokens[0]]):
            print('不满足文法')
            break
        else:
            use_p = generator_list[ll1_table.loc[stack[-1],tokens[0]]]
            stack.pop()
            temp = use_p[1].copy()
            if temp[0] != '@':
                temp.reverse()
                stack.extend(temp)

#%%
out.close()
'''
update = 1
while update == 1:
    update = 0
    for l_sym in generator:
        #TODO:处理空符号，尤其是前面所有的均为空
        for each in generator[l_sym]:
            if each[0] in non_set:
                #如果添加导致改变，设置标志位
                temp = first[l_sym].union( first[each[0]] - set('@') )
                if first[l_sym] != temp:
                    update = 1
                    first[l_sym] = temp
'''


import numpy as np
import pytest
import scipy
import tensorly as tl
from numpy.core.numeric import allclose
from tensorly.testing import assert_array_equal

from matcouply import _utils as utils

def test_is_iterable():
    # Test with some objects that are iterable
    class TestIterable:
        def __iter__(self):
            return self

        def __next__(self):
            return 1

    test_iterables = [
        [1, 2, 3, 4],
        (1, 2, 3, 4),
        "iterablestring",
        {1: "value1", 2: "value2"},
        range(5),
        TestIterable(),
    ]
    for test_iterable in test_iterables:
        assert utils.is_iterable(test_iterable)

    # Test with some objects that arent't iterable
    def test_function(x):
        return x

    class TestNotIterable:
        pass

    test_not_iterables = [1, 3.14, test_function, TestNotIterable()]
    for test_not_iterable in test_not_iterables:
        assert not utils.is_iterable(test_not_iterable)

@pytest.mark.parametrize("svd", ["numpy_svd", "truncated_svd"])
def test_get_svd(rng, svd):
    X = rng.standard_normal(size=(10, 20))
    U1, s1, Vh1 = scipy.linalg.svd(X)
    svd_fun = utils.get_svd(svd)
    U2, s2, Vh2 = svd_fun(tl.tensor(X))
    U2, s2, Vh2 = tl.to_numpy(U2), tl.to_numpy(s2), tl.to_numpy(Vh2)

    # Check singular values are the same
    assert allclose(s1, s2)

    # Check that first 10 (rank) singular vectors are equal or flipped
    U1TU2 = U1.T @ U2
    Vh1Vh2T = Vh1 @ Vh2.T
    Vh1Vh2T = Vh1Vh2T[:10, :10]
    assert allclose(U1TU2, Vh1Vh2T, atol=1e-6)  # low tolerance due to roundoff errors
    assert allclose(U1TU2 * Vh1Vh2T, np.eye(U1TU2.shape[0]))

def test_get_svd_fails_with_invalid_svd_name():
    with pytest.raises(ValueError):
        utils.get_svd("THIS_IS_NOT_A_VALID_SVD")

def test_get_shapes(rng, random_ragged_cmf):
    # Small manual test
    matrices = [tl.zeros((1, 2)), tl.zeros((3, 4)), tl.zeros((5, 6))]
    matrix_shapes = utils.get_shapes(matrices)
    assert matrix_shapes[0] == (1, 2)
    assert matrix_shapes[1] == (3, 4)
    assert matrix_shapes[2] == (5, 6)
    assert len(matrix_shapes) == 3

    # Test on random ragged cmf
    cmf, shapes, rank = random_ragged_cmf
    matrix_shapes = utils.get_shapes(cmf.to_matrices())
    for matrix_shape, shape in zip(matrix_shapes, shapes):
        assert matrix_shape == shape

def test_get_padded_tensor_shape(rng, random_ragged_cmf):
    cmf, shapes, rank = random_ragged_cmf

    I = len(shapes)
    J = max([shape[0] for shape in shapes])
    K = shapes[0][1]

    assert (I, J, K) == utils.get_padded_tensor_shape(cmf.to_matrices())

    matrices_different_columns = [
        tl.tensor(rng.standard_normal(size=(3, 4))),
        tl.tensor(rng.standard_normal(size=(5, 6))),
        tl.tensor(rng.standard_normal(size=(5, 6))),
    ]
    with pytest.raises(ValueError):
        utils.get_padded_tensor_shape(matrices_different_columns)

def test_create_padded_tensor(rng, random_ragged_cmf):
    cmf, shapes, rank = random_ragged_cmf
    matrices = cmf.to_matrices()
    padded_tensor = utils.create_padded_tensor(matrices)

    I = len(shapes)
    J = max([shape[0] for shape in shapes])
    K = shapes[0][1]

    assert (I, J, K) == tl.shape(padded_tensor)

    for i, (matrix, shape) in enumerate(zip(matrices, shapes)):
        assert_array_equal(padded_tensor[i, : shape[0], :], matrix)
        assert_array_equal(padded_tensor[i, shape[0] :, :], 0)

@pytest.mark.parametrize("shape", [(10, 3), (10, 10)])
def test_scipy_svd(rng, shape):
    svd = utils.get_svd("scipy")
    X = rng.standard_normal(shape)
    U, s, Vh = svd(X)
    assert U.shape == (shape[0], shape[0])
    assert s.shape == (min(shape),)
    assert Vh.shape == (shape[1], shape[1])

    U, s, Vh = svd(X, n_eigenvecs=3)
    assert U.shape == (shape[0], 3)
    assert s.shape == (3,)
    assert Vh.shape == (3, shape[1])

    U, s, Vh = svd(X, n_eigenvecs=2)
    assert U.shape == (shape[0], 2)
    assert s.shape == (2,)
    assert Vh.shape == (2, shape[1])