from PIL import Image, ImageChops, ImageMath, ImageOps, ImageStat, ImageFilter
import math, operator
import numpy as np
import random

class Agent:

    def __init__(self):
        pass

    skipping = 0

    def Solve(self, problem):

        if problem.problemType == '2x2':

            print("problem name: " + problem.name)
            prob_fig = {}
            ans_fig = {}
            prob_array = {}
            ans_array = {}

            rav_list = ['A', 'B', 'C']
            ans_list = ['1', '2', '3', '4', '5', '6']

            for key in problem.figures:
                fig = problem.figures[key]
                image = Image.open(fig.visualFilename).convert('1')
                arrayImage = self.centerImageArray(np.array(image))
                if key in rav_list:
                    prob_fig[key] = image
                    prob_array[key] = arrayImage
                if key in ans_list:
                    ans_fig[key] = image
                    ans_array[key] = arrayImage

            ATransformations = [
            prob_fig['A'].transpose(Image.FLIP_LEFT_RIGHT),
            prob_fig['A'].transpose(Image.FLIP_TOP_BOTTOM),
            prob_fig['A'].transpose(Image.ROTATE_90),
            prob_fig['A'].transpose(Image.ROTATE_180),
            prob_fig['A'].transpose(Image.ROTATE_270)
            ]

            BTransformations = [
            prob_fig['B'].transpose(Image.FLIP_LEFT_RIGHT),
            prob_fig['B'].transpose(Image.FLIP_TOP_BOTTOM),
            prob_fig['B'].transpose(Image.ROTATE_90),
            prob_fig['B'].transpose(Image.ROTATE_180),
            prob_fig['B'].transpose(Image.ROTATE_270)
            ]

            CTransformations = [
            prob_fig['C'].transpose(Image.FLIP_LEFT_RIGHT),
            prob_fig['C'].transpose(Image.FLIP_TOP_BOTTOM),
            prob_fig['C'].transpose(Image.ROTATE_90),
            prob_fig['C'].transpose(Image.ROTATE_180),
            prob_fig['C'].transpose(Image.ROTATE_270)
            ]

            if self.equal_images(prob_fig['A'], prob_fig['B'])[0]:
                print('A == B')
                for i in range(1,7):
                    if self.equal_images(ans_fig[str(i)], prob_fig['C'])[0]:
                        answer = i
                        print(answer)
                        return answer

            elif self.equal_images(prob_fig['A'], prob_fig['C'])[0]:
                for i in range(1,7):
                    if self.equal_images(ans_fig[str(i)], prob_fig['B'])[0]:
                        answer = i
                        print(answer)
                        return answer

            elif self.equal_images(prob_fig['B'], self.black_white_pixel_flip(ImageChops.difference(prob_fig['A'],prob_fig['B'])))[0]:
                for i in range(1,9):
                    if self.equal_images(ans_fig[str(i)], self.black_white_pixel_flip(ImageChops.difference(prob_fig['C'],ans_fig[str(i)])))[0]:
                        print(i)
                        return i

            elif self.equal_images(prob_fig['C'], self.black_white_pixel_flip(ImageChops.difference(prob_fig['A'],prob_fig['C'])))[0]:
                for i in range(1,9):
                    if self.equal_images(ans_fig[str(i)], self.black_white_pixel_flip(ImageChops.difference(prob_fig['B'],ans_fig[str(i)])))[0]:
                        print(i)
                        return i

            else:
                for x in range(len(ATransformations)):
                    if self.equal_images(ATransformations[x], prob_fig['B'])[0]:
                        trans = x
                        for i in range(1,7):
                            if self.equal_images(ans_fig[str(i)], CTransformations[trans])[0]:
                                print(i)
                                return i

                for x in range(len(ATransformations)):
                    if self.equal_images(ATransformations[x], prob_fig['C'])[0]:
                        trans = x
                        for i in range(1,7):
                            if self.equal_images(ans_fig[str(i)], BTransformations[trans])[0]:
                                print(i)
                                return i

                else:
                    answer = random.randint(1,7)
                    print(answer)
                    return answer

        if problem.problemType == '3x3':
            print("problem name: " + problem.name)

            prob_fig = {}
            ans_fig = {}
            prob_array = {}
            ans_array = {}

            rav_list = ['A', 'B', 'C','D','E','F','G','H']
            ans_list = ['1', '2', '3', '4', '5', '6','7','8']

            for key in problem.figures:
                fig = problem.figures[key]
                image = Image.open(fig.visualFilename).convert('1')
                arrayImage = self.centerImageArray(np.array(image))
                if key in rav_list:
                    prob_fig[key] = image
                    prob_array[key] = arrayImage
                if key in ans_list:
                    ans_fig[key] = image
                    ans_array[key] = arrayImage

            rowAB = ImageChops.add(prob_fig['A'], prob_fig['B'])
            rowBC = ImageChops.add(prob_fig['B'], prob_fig['C'])
            rowDE = ImageChops.add(prob_fig['D'], prob_fig['E'])
            rowEF = ImageChops.add(prob_fig['E'], prob_fig['F'])

            colAD =ImageChops.multiply(prob_fig['A'], prob_fig['D'])
            colADG= ImageChops.multiply(colAD, prob_fig['G'])

            colBE =ImageChops.multiply(prob_fig['B'], prob_fig['E'])
            colBEH= ImageChops.multiply(colBE, prob_fig['H'])

            ab = ImageChops.multiply(prob_fig['A'],prob_fig['B'])
            bc = ImageChops.multiply(prob_fig['B'], prob_fig['C'])
            ac = ImageChops.multiply(prob_fig['A'],prob_fig['C'])
            df = ImageChops.multiply(prob_fig['D'],prob_fig['F'])
            abc = ImageChops.multiply(ab,prob_fig['C'])
            de = ImageChops.multiply(prob_fig['D'],prob_fig['E'])
            de_F=ImageChops.multiply(de,prob_fig['F'])
            ef = ImageChops.multiply(prob_fig['E'],prob_fig['F'])

            difAB=self.black_to_white_pixels(ImageChops.difference(prob_fig['A'], prob_fig['B']))
            difDE=self.black_to_white_pixels(ImageChops.difference(prob_fig['D'], prob_fig['E']))

            if self.equal_images(prob_fig['A'], prob_fig['B'])[0] and self.equal_images(prob_fig['B'], prob_fig['C'])[0]:
                if self.equal_images(prob_fig['D'], prob_fig['E'])[0] and self.equal_images(prob_fig['E'], prob_fig['F'])[0]:
                    for i in range(1,9):
                        if self.equal_images(prob_fig['H'],ans_fig[str(i)])[0]:
                            return i
            elif self.equal_images(prob_fig['A'],prob_fig['E'])[0]:
                for i in range(1,9):
                    if self.equal_images(prob_fig['E'],ans_fig[str(i)])[0]:
                        return i
            elif ((self.equal_images(prob_fig['A'], prob_fig['D'])[0] or self.equal_images(prob_fig['A'], prob_fig['E'])[0] or self.equal_images(prob_fig['A'],prob_fig['F'])[0]) \
                    and (self.equal_images(prob_fig['B'], prob_fig['D'])[0] or self.equal_images(prob_fig['B'], prob_fig['E'])[0] or self.equal_images(prob_fig['B'], prob_fig['F'])[0]) \
                    and (self.equal_images(prob_fig['C'], prob_fig['D'])[0] or self.equal_images(prob_fig['C'], prob_fig['E'])[0] or self.equal_images(prob_fig['C'], prob_fig['F'])[0])):
                if self.equal_images(prob_fig['A'], prob_fig['G'])[0] or self.equal_images(prob_fig['A'],prob_fig['H'])[0]:
                    if self.equal_images(prob_fig['B'], prob_fig['G'])[0] or self.equal_images(prob_fig['B'],prob_fig['H'][0]):
                        if self.equal_images(prob_fig['C'], prob_fig['G'])[0] or self.equal_images(prob_fig['C'],prob_fig['H'])[0]:
                            print("need to chose another strategy")
                        else:
                            missing_figure ='C'
                    else:
                        missing_figure ='B'
                else:
                    missing_figure = "A"

                for i in range(1, 9):
                    if self.equal_images(prob_fig[missing_figure], ans_fig[str(i)])[0]:
                        return i

            elif self.equal_images(rowAB, rowBC)[0] and self.equal_images(rowDE, rowEF)[0]:
                rowCF = ImageChops.add(prob_fig["C"], prob_fig["F"])
                rowGH = ImageChops.add(prob_fig["G"], prob_fig["H"])
                rowHF = ImageChops.multiply(prob_fig["H"], prob_fig["F"])
                answers ={}

                for i in range(1, 9):
                    candidate = ImageChops.add(rowCF, ans_fig[str(i)])
                    candidate2 = ImageChops.add(rowGH, ans_fig[str(i)])

                    if self.equal_images(rowCF, candidate)[0] and self.equal_images(rowGH, candidate2)[0]:

                        answers[i]= ans_fig[str(i)]

                if len(answers)!=1:
                    if self.transformation_equality(prob_fig):
                        return self.apply_transform(prob_fig, ans_fig)
                    else:
                        return self.no_duplicates(prob_fig, ans_fig)
                else:
                    for keys in answers:
                        answer = answers[keys][0]
                        return answer
                return -1

            elif self.equal_images(colADG, colBEH)[0]:
                colAD= ImageChops.multiply(prob_fig['A'], prob_fig['D'])
                colADG= ImageChops.multiply(colAD, prob_fig['G'])
                colCF= ImageChops.multiply(prob_fig['C'], prob_fig['F'])

                for i in range(1, 9):
                    candidate = ImageChops.multiply(colCF, ans_fig[str(i)])
                    if self.equal_images(candidate,colADG)[0]:
                        return i
                return -1
            if self.equal_images(ab, prob_fig['C'])[0] and self.equal_images(de, prob_fig['F'])[0]:
                i = 1
                answer = self.combo_answer(prob_fig, ans_fig, i)
                return answer
            elif self.equal_images(ac, prob_fig['B'])[0] and self.equal_images(df, prob_fig['E'])[0]:
                i = 2
                answer = self.combo_answer(prob_fig, ans_fig, i)
                return answer
            elif self.equal_images(abc, de_F)[0]:
                i = 0
                answer = self.combo_answer(prob_fig, ans_fig, i)
                return answer
            elif self.equal_images(difAB, prob_fig['C'])[0] and self.equal_images(difDE, prob_fig['F'])[0]:
                answer = self.ans_difference(prob_fig, ans_fig)
                return answer
            elif self.transformation_equality(prob_fig):
                return self.apply_transform(prob_fig, ans_fig)
            else:
                try:
                    answer = self.no_duplicates(prob_fig, ans_fig)
                    return answer
                except:
                    answer = random.randint(1,9)
                    return answer

            return -1

    @staticmethod
    def equal_images(im1, im2):
        dif = sum(abs(p1 - p2) for p1, p2 in zip(im1.getdata(), im2.getdata()))

        ncomponents = im1.size[0] * im1.size[1] * 3
        dist = (dif / 255.0 * 100) / ncomponents
        im1__getcolors = im1.getcolors()
        im2_getcolors = im2.getcolors()
        black1 = (10000, 0)
        if len(im1__getcolors) > 1:
            black, white = im1__getcolors

        else:
            if im1__getcolors[0][1] == 255:
                white = im1__getcolors
                black = (0, 0)
            else:
                black = im1__getcolors
                white = (0, 255)

        if len(im2_getcolors) > 1:
            black1, white1 = im2_getcolors
        else:
            if im2_getcolors[0][1] == 255:
                white1 = im2_getcolors
                black1 = (0, 0)
            else:
                black1 = im2_getcolors
                white1 = (0, 255)

        stats = {"dist": dist, "blk": abs(black[0] - black1[0])}

        return (dist<1.1 and abs(black[0]-black1[0])<105), stats
        # return (dist<1.1 and abs(black[0]-black1[0])<105 and abs(white[0]-white1[0]<100)), stats

    def transformation_equality(self, prob_fig):
        sharedAB=self.pixel_comparison(prob_fig["A"], prob_fig["B"])[0]
        sharedDE=self.pixel_comparison(prob_fig["D"], prob_fig["E"])[0]
        return self.equal_images(sharedAB, prob_fig["C"])[0] and self.equal_images(sharedDE, prob_fig["F"])[0]

    def apply_transform(self, prob_fig, ans_fig):
        sharedGE=self.pixel_comparison(prob_fig["G"], prob_fig["H"])[0]
        for i in range(1, 9):
            if self.equal_images(sharedGE, ans_fig[str(i)])[0]:
                return int(i)
        else:
            try:
                answer = self.no_duplicates(prob_fig, ans_fig)
                return answer
            except:
                answer = random.randint(1,9)
                return answer
            return -1

    def combo_answer(self, prob_fig, ans_fig, index):
        de = ImageChops.multiply(prob_fig["D"],prob_fig["E"])
        gh = ImageChops.multiply(prob_fig["G"],prob_fig["H"])
        de_F=ImageChops.multiply(de,prob_fig["F"])

        if index == 0:
            for i in range (1,9):
                candidate= ImageChops.multiply(gh, ans_fig[str(i)])
                if self.equal_images(candidate,de_F)[0]:
                    return i
            return self.no_duplicates(prob_fig, ans_fig)

        if index == 1:
            for i in range (1,9):
                if self.equal_images(gh,ans_fig[str(i)])[0]:
                    return i
            return self.no_duplicates(prob_fig, ans_fig)

        if index == 2:
            for i in range (1,9):
                candidate= ImageChops.multiply(prob_fig["G"], ans_fig[str(i)])
                if self.equal_images(candidate,prob_fig["H"])[0]:
                    return i
            return self.no_duplicates(prob_fig, ans_fig)

    def ans_difference(self, prob_fig, ans_fig):
        difGH=self.black_to_white_pixels(ImageChops.difference(prob_fig["H"], prob_fig["G"]))
        for i in range (1,9):
            if self.equal_images(ans_fig[str(i)],difGH)[0]:
                return i
        return self.no_duplicates(prob_fig, ans_fig)

    def no_duplicates(self, prob_fig, ans_fig):
        figs =["A","B","C","D","E","F","G", "H"]
        answers=[1,2,3,4,5,6,7,8]
        for fig in figs:
            for i in range(1,9):
                if self.equal_images(prob_fig[fig], ans_fig[str(i)])[0] :
                    if i in answers:
                        answers.remove(i)

        if len(answers)==1:
            return answers[0]
        elif self.skipping:
            return -1
        return answers[0]

    def black_to_white_pixels(self, image):
        inverted = Image.new("1", image.size, "white")
        for x in range(0, image.size[1]):
            for y in range(0, image.size[0]):
                p1 = image.getpixel((x, y))
                if (p1 == 0):
                    inverted.putpixel((x, y), 255)
                else:
                    inverted.putpixel((x, y), 0)

        return inverted

    def centerImageArray(self,figArray):
        figImage = Image.fromarray(figArray, "L")

        # mask
        threshold=128
        mask = figImage.point(lambda p: p < threshold and 255)

        # find edges
        edges = mask.filter(ImageFilter.FIND_EDGES)
        box = edges.getbbox()
        edges = edges.crop(box)

        # center in new image-figure
        tempImg = Image.new("L", figImage.size)

        width, height = edges.size
        fwidth, fheight = figImage.size

        tempImg.paste(edges, ((fwidth - width) // 2, (fheight - height) // 2))

        return np.array(tempImg)

    def pixel_comparison(self, img1, img2):
        common = Image.new("1", img1.size, "white")

        delta = Image.new("1", img1.size, "white")
        for x in range(0, common.size[1]):
            for y in range(0, common.size[0]):
                p1 = img1.getpixel((x, y))
                p2 = img2.getpixel((x, y))
                if (p1 == 0 and p2 == 0):
                    common.putpixel((x, y), 0)

                elif p1 == 255 and p2 == 255:
                    common.putpixel((x, y), 255)

                else:

                    delta.putpixel((x, y), 0)

        return common, delta

    def center_lists(self, list1):

        listUpdate = [self.centerImageArray(np.array(list1[x])) for x in range(len(list1))]
        return listUpdate

    def black_white_pixel_flip(self, image):
        inverted = Image.new("1", image.size, "white")
        for x in range(0, image.size[1]):
            for y in range(0, image.size[0]):
                p1 = image.getpixel((x, y))
                if (p1 == 0):
                    inverted.putpixel((x, y), 255)
                else:
                    inverted.putpixel((x, y), 0)

        return inverted

    def make_transparent(self,img):

        pixdata = img.load()

        width, height = img.size
        for y in range(height):
            for x in range(width):
                if pixdata[x, y] == (255, 255, 255, 255):
                    pixdata[x, y] = (255, 255, 255, 0)

    def pixels_comparison(self, figArray, figArray2):
        # in 'L', 255 is white
        white1 = np.count_nonzero(figArray)
        black1 = figArray.size - white1
        white2 = np.count_nonzero(figArray2)
        black2 = figArray2.size - white2
        difference = abs(black2 - black1)
        return difference

    def get_pixel_count(self, figArray):
        white = np.count_nonzero(figArray)
        black = figArray.size - white
        return black

    def find_nearest_neighbor(self, ans_array, array1, array2, array3):
        answer_compare = [self.pixels_comparison(x,array1) for x in ans_array]
        prob_pixels = self.pixels_comparison(array2,array3)
        nearest_list = [abs(prob_pixels - x) for x in answer_compare]
        nearestNeighbor = np.argmin(nearest_list)
        answer = nearestNeighbor + 1
        print(answer)
        return answer

    def centerImageArray(self,figArray):
        figImage = Image.fromarray(figArray, "L")

        # mask
        threshold=128
        mask = figImage.point(lambda p: p < threshold and 255)

        # find edges
        edges = mask.filter(ImageFilter.FIND_EDGES)
        box = edges.getbbox()
        edges = edges.crop(box)

        # center in new image-figure
        tempImg = Image.new("L", figImage.size)

        width, height = edges.size
        fwidth, fheight = figImage.size

        tempImg.paste(edges, ((fwidth - width) // 2, (fheight - height) // 2))

        return np.array(tempImg)

    def MSE(self,arrayA, arrayB):
        return np.square(arrayA - arrayB).mean()

    def compare_images(self, arrayA, arrayB):
        # compute the mean squared error and structural similarity
        # index for the images
        m = self.MSE(arrayA, arrayB)

        return m

    def structural_similarity(self, array1, array2):
        ux = np.mean(array1)
        uy = np.mean(array2)
        uxx = ux*ux
        uyy = uy*uy

        xy = array1*array2
        uxy = np.mean(xy)

        xstd = np.std(array1)
        ystd = np.std(array2)
        xystd = np.std(xy)

        xv = uxx - ux * ux
        yv = uyy - uy * uy
        xyv = uxy - ux * uy

        C1 = (.8*255)**2
        C2 = (.9*255)**2

        ssim = (((2*ux*uy + C1)*(2*xyv + C2)/((uxx + uyy + C1)*(xv + yv + C2))))
        return ssim

    def compare_pixels(self, list1, list2):

        if list1 != 0:
            m = (abs(list1 - list2)/list1)
            return m
        else:
            m = (abs(list1 - list2)/list2)
            return m

    def find_min(self, ans_array, array2):
        answer_compare = [self.compare_images(ans_array[str(x)],array2) for x in range(1,9)]
        answer_compare1 = [self.pixels_comparison(ans_array[str(x)],array2) for x in range(1,9)]
        min_array = np.argmin(answer_compare)
        min_array1 = np.argmin(answer_compare1)

        if min_array == min_array1:
            return min_array
        else:
            return min_array1

    def find_second_min_array(self, ans_array, array2):
        answer_compare = [self.compare_images(ans_array[str(x+ 1)],array2) for x in range(len(ans_array))]
        answer_compare1 = answer_compare
        answer_compare1.sort()
        length = len(answer_compare)
        second_smallest = answer_compare1[length - 2]

        for i in range(length):
            if answer_compare[i] == second_smallest:
                answer = i

        answer_comparepix = [self.pixels_comparison(ans_array[str(x+ 1)],array2) for x in range(len(ans_array))]
        answer_comparepix1 = answer_comparepix
        answer_comparepix1.sort()
        length1 = len(answer_comparepix)
        second_smallestpix = answer_comparepix1[length - 2]

        for i in range(length1):
            if answer_comparepix[i] == second_smallestpix:
                answerpix = i

                resultlist = [answer,answerpix]
                return resultlist

    def find_second_min_list(self, list1):

        list1.sort()
        length = len(list1)
        second_smallest = list1[length - 2]

        for i in range(length):
            if list1[i] == second_smallest:
                answer = i

                return answer

    def sum_of_row(self, arrayA, arrayB, arrayC):

        A = self.get_pixel_count(arrayA)
        B = self.get_pixel_count(arrayB)
        C = self.get_pixel_count(arrayC)

        sumABC = A + B + C

        return sumABC

    def sum_of_pair(self, arrayA, arrayB):

        A = self.get_pixel_count(arrayA)
        B = self.get_pixel_count(arrayB)

        sumAB = A + B

        return sumAB

    def sum_of_ans(self, arrayG, arrayH, ans_array):

        G = self.get_pixel_count(arrayG)
        H = self.get_pixel_count(arrayH)

        sumOfAns = [(G + H + self.get_pixel_count(x)) for x in ans_array]

        return sumOfAns

    def diff_of_row(self, a, b, c):

        ab = self.pixels_comparison( a, b)
        bc = self.pixels_comparison( b, c)

        difference = abs(ab - bc)

        return difference

    def diff_of_ans(self, g, h, ans_array):

        gh = self.pixels_comparison( g, h)

        diffOfAns = [abs(gh - self.pixels_comparison(h, ans_array[x])) for x in ans_array]

        return diffOfAns

    def find_nearest_list(self, list1, list2,list3,list4, four):

        rowListAD = [abs(list1[0] - x) for x in list4]
        rowListGH = abs(list1[0] - four)

        comboListAD = [abs(list1[1] - x) for x in list4]

        colListAB = [abs(list1[2] - x) for x in list2]
        colListAC = [abs(list1[2] - x) for x in list3]

        diagListAB = [abs(list1[3] - x) for x in list2]
        diagListAC = [abs(list1[3] - x) for x in list3]

        rowminAD = np.argmin(rowListAD)
        colminAB = np.argmin(colListAB)
        colminAC = np.argmin(colListAC)
        diagminAB = np.argmin(diagListAB)
        diagminAC = np.argmin(diagListAC)
        combolistmin = np.argmin(comboListAD)

        if diagminAB == 1 or diagminAB == 3:
            print('Cyclic pattern')
            return 1
        elif rowListAD == 0:
            if rowListGH == 0:
                print('pattern same ab2de2gh and bc2ef2hi')
                return 2
        elif combolistmin == 1:
            print('Combo or pattern same across rows')
            return 3
        else:
            return 4

    def nearest_answerHI(self, list1,list2):

        ansListHI = [abs(list1[0] - x) for x in list2]

        minAns = np.argmin(ansListHI)
        answer = minAns + 1
        print(answer)
        return answer

    def nearest_answerGI(self, list1,list2):

        ansListGI = [abs(list1[1] - x) for x in list2]

        minAns = np.argmin(ansListGI)

        answer = minAns + 1
        return answer

    def nearest_answerEI(self, list1,list2):

        ansListEI = [abs(list1[3] - x) for x in list2]

        minAns = np.argmin(ansListEI)

        answer = minAns + 1
        return answer

    def nearest_answerFI(self, list1,list2):

        ansListFI = [abs(list1[2] - x) for x in list2]

        minAns = np.argmin(ansListFI)

        answer = minAns + 1
        return answer

    def greater_than_list(self, ans_array, prob_array):

        if self.pixels_comparison(prob_array['A'],prob_array['B']) < self.pixels_comparison(prob_array['B'],prob_array['C']):
            i = 0
            max_list = []
            while i < len(ans_array):
                m = self.pixels_comparison(prob_array['H'],ans_array[str(i)])
                if m > self.pixels_comparison(prob_array['G'],prob_array['H']):
                    max_list.append(m)
                    i +=1
                    return m


#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Importing Libraries
"""
import numpy as np
import glob
import os
import cv2
from sklearn.naive_bayes import GaussianNB
from sklearn.metrics import accuracy_score
from sklearn.preprocessing import StandardScaler
import pickle
import time
from sklearn.metrics import confusion_matrix

"""
Creating object for Standard Scaler
"""
scaler = StandardScaler()

"""
Loading and preprocessing the training dataset
"""
fruit_training = []
labels_training = []
for fruit_dir_path in glob.glob("fruits-360/Training/*"):
    fruit_label = fruit_dir_path.split("/")[-1]
    for image_path in glob.glob(os.path.join(fruit_dir_path, "*.jpg")):
        image = cv2.imread(image_path, cv2.IMREAD_COLOR)
        image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
        image = cv2.resize(image, (50, 50))
        fruit_training.append(image.flatten())
        labels_training.append(fruit_label)
fruit_training = np.array(fruit_training)
labels_training = np.array(labels_training)
fruit_training = scaler.fit_transform(fruit_training)
label_to_id_dict = {v: i for i,v in enumerate(np.unique(labels_training))}
id_to_label_dict = {v: k for k, v in label_to_id_dict.items()}
label_ids_training = np.array([label_to_id_dict[x] for x in labels_training])

"""
Loading and preprocessing the test dataset
"""
fruit_test = []
labels_test = []
for fruit_dir_path in glob.glob("fruits-360/Test/*"):
    fruit_label = fruit_dir_path.split("/")[-1]
    for image_path in glob.glob(os.path.join(fruit_dir_path, "*.jpg")):
        image = cv2.imread(image_path, cv2.IMREAD_COLOR)
        image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
        image = cv2.resize(image, (50, 50))
        fruit_test.append(image.flatten())
        labels_test.append(fruit_label)
fruit_test = np.array(fruit_test)
fruit_test = scaler.transform(fruit_test)
labels_test = np.array(labels_test)
label_to_id_dict = {v: i for i,v in enumerate(np.unique(labels_test))}
id_to_label_dict = {v: k for k, v in label_to_id_dict.items()}
label_ids_test = np.array([label_to_id_dict[x] for x in labels_test])

"""
    Naive Bayes Classifier
"""
start_time = time.time()
classifier = GaussianNB()
classifier = classifier.fit(fruit_training, label_ids_training)
print("--- %s seconds ---" % (time.time() - start_time))

"""
Prediction
"""
y_pred = classifier.predict(fruit_test)
precision = accuracy_score(y_pred, label_ids_test) * 100
print("Accuracy with Naive Bayes: {0:.6f}".format(precision))

"""
Confusion Matrix
"""
cm = confusion_matrix(label_ids_test, y_pred)

"""
Save the model to disk
"""
filename = 'finalized_model.sav'
pickle.dump(classifier, open(filename, 'wb'))
filename = 'finalized_scaler.sav'
pickle.dump(scaler, open(filename, 'wb'))


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Jan 16 10:24:26 2019

@author: evertoncolling
@email: evertoncolling@gmail.com
@licence: MIT
"""

from __future__ import print_function, division
from ipywidgets import widgets as wi
from IPython.display import display
import threading
import time
import numpy as np
from gekko import GEKKO
import bqplot as bq
from tclab import TCLab

class GUI(object):
    """
    Class that defines the _GUI applications
    """
    def __init__(self):
        """
        Initialize the _GUI elements
        """
        #######################################################################
        #                                               PLOTTING CONFIGURATION
        #######################################################################
        colors = [
            '#1f77b4',  # T1
            '#5ebdff',  # T1_SP
            '#d62728',  # T2
            '#ff7f0e',  # T2_SP
            '#1f77b4',  # Q1
            '#d62728'   # Q2
        ]

        # Create scales
        t_sc = bq.LinearScale()
        T_sc = bq.LinearScale(min=20., max=60.)
        Q_sc = bq.LinearScale(min=0., max=100.)
        T_sc_set = {'x': t_sc, 'y': T_sc}
        Q_sc_set = {'x': t_sc, 'y': Q_sc}

        # Create Axis
        ax_x1 = bq.Axis(label="", scale=t_sc, visible=False)  # upper
        ax_x2 = bq.Axis(label="time [min]", scale=t_sc)       # down
        ax_T1 = bq.Axis(label="Temperature 1 [C]", scale=T_sc,
                        orientation="vertical", label_color=colors[0])
        ax_T2 = bq.Axis(label="Temperature 2 [C]", scale=T_sc,
                        orientation="vertical", label_color=colors[2])
        ax_Q = bq.Axis(label="Heater Output [%]", scale=Q_sc,
                       orientation="vertical", label_color="black")

        # Create Lines/Markers
        self._T1_meas = bq.Scatter(x=[], y=[], scales=T_sc_set,
                                   marker='circle', colors=[colors[0]],
                                   display_legend=False, default_size=20)
        self._T2_meas = bq.Scatter(x=[], y=[], scales=T_sc_set,
                                   marker='circle', colors=[colors[2]],
                                   display_legend=False, default_size=20)

        self._T1_set_point = bq.Lines(x=[], y=[], scales=T_sc_set,
                                      stroke_width=4, colors=[colors[1]],
                                      interpolation='step-before',
                                      display_legend=False)
        self._T2_set_point = bq.Lines(x=[], y=[], scales=T_sc_set,
                                      stroke_width=4, colors=[colors[3]],
                                      interpolation='step-before',
                                      display_legend=False)

        self._u1 = bq.Lines(x=[], y=[], scales=Q_sc_set,
                            stroke_width=2, colors=[colors[4]],
                            interpolation='step-before',
                            display_legend=True, labels=['Heater 1'])
        self._u2 = bq.Lines(x=[], y=[], scales=Q_sc_set,
                            stroke_width=2, colors=[colors[5]],
                            interpolation='step-before',
                            display_legend=True, labels=['Heater 2'])

        # Mix everything and create figures
        fig_lay_up = wi.Layout(width="400px", height="240px")
        fig_lay_down = wi.Layout(width="400px", height="290px")

        box1 = dict(top=10, bottom=0, left=60, right=5)
        box2 = dict(top=10, bottom=0, left=60, right=20)
        box3 = dict(top=10, bottom=50, left=60, right=10)

        fig1 = bq.Figure(layout=fig_lay_up, axes=[ax_x1, ax_T1],
                         marks=[self._T1_meas, self._T1_set_point],
                         fig_margin=box1)

        fig2 = bq.Figure(layout=fig_lay_up, axes=[ax_x1, ax_T2],
                         marks=[self._T2_meas, self._T2_set_point],
                         fig_margin=box2)

        fig3 = bq.Figure(layout=fig_lay_down, axes=[ax_x2, ax_Q],
                         marks=[self._u1, self._u2], fig_margin=box3,
                         legend_location="left",
                         legend_style={"fill": "white",
                                       "fill-opacity": "0.7",
                                       "width": "130px"})

        #######################################################################
        #                                             SPACING WIDGETS CRIATION
        #######################################################################
        v_space = wi.Label(value="", layout=wi.Layout(height='2px'))
        h_space = wi.Label(value="", layout=wi.Layout(width='2px'))

        #######################################################################
        #                                         VARYING ARRAYS TO STORE DATA
        #######################################################################
        self._t = np.array([])
        self._Q1 = np.array([])
        self._Q2 = np.array([])
        self._T = np.array([[]]).reshape((0, 2))
        self._SP_T1 = np.array([])
        self._SP_T2 = np.array([])

        #######################################################################
        #                                                           PARAMETERS
        #######################################################################
        self._delta_t = 4.0
        self._maxtime = int(500/self._delta_t)
        self._T1_SP = 30
        self._T2_SP = 30
        self._Q10 = 0
        self._Q20 = 0
        self._flag = False
        self._sleep = 0.5
        self._Tc0 = np.array([293.15, 293.15])

        self._q1_dt_on_off = 0.1
        self._q2_dt_on_off = 0.1

        self._pid1_gain = 10.
        self._pid1_reset = 50.
        self._pid1_rate = 1.

        self._pid2_gain = 10.
        self._pid2_reset = 50.
        self._pid2_rate = 1.

        self._SOLVER = '1 - APOPT'
        self._CVTYPE = '1 - Deadband'

        self._T1_dt = 0.1
        self._T1_tau = 10.
        self._T2_dt = 0.1
        self._T2_tau = 10.

        self._Q1_DMAX = 30.
        self._Q1_DCOST = 1.
        self._Q2_DMAX = 30.
        self._Q2_DCOST = 1.

        #######################################################################
        #                                              OUTPUT WIDGETS CRIATION
        #######################################################################
        style = {'description_width': 'initial'}

        self._PT1 = wi.FloatProgress(value=self._Tc0[0]-273.15, min=0,
                                     max=100.0,
                                     description='PV (°C):',
                                     bar_style='warning',
                                     orientation='horizontal',
                                     style=style)
        self._LT1 = wi.Label(value=str(self._PT1.value))
        wi.jslink((self._PT1, 'value'), (self._LT1, 'value'))

        self._PT2 = wi.FloatProgress(value=self._Tc0[1]-273.15, min=0,
                                     max=100.0,
                                     description='PV (°C):',
                                     bar_style='warning',
                                     orientation='horizontal',
                                     style=style)
        self._LT2 = wi.Label(value=str(self._PT2.value))
        wi.jslink((self._PT2, 'value'), (self._LT2, 'value'))

        #######################################################################
        #                                         INTERACTION WIDGETS CREATION
        #######################################################################
        self._wQ1 = wi.FloatSlider(value=self._Q10, min=0, max=100.0, step=0.5,
                                   description='Q1 (%):',
                                   continuous_update=False,
                                   orientation='horizontal',
                                   readout=False, style=style,
                                   layout=wi.Layout(width='230px'))
        self._tQ1 = wi.BoundedFloatText(value=self._Q10, min=0, max=100.0,
                                        step=0.5,
                                        layout=wi.Layout(width='60px'))
        wi.jslink((self._wQ1, 'value'), (self._tQ1, 'value'))

        self._bQ1 = wi.Button(description='Set',
                              layout=wi.Layout(width='50px'))
        self._bQ1.on_click(self._Q1_click)

        self._wQ2 = wi.FloatSlider(value=self._Q20, min=0, max=100.0, step=0.5,
                                   description='Q2 (%):',
                                   continuous_update=False,
                                   orientation='horizontal',
                                   readout=False, style=style,
                                   layout=wi.Layout(width='230px'))
        self._tQ2 = wi.BoundedFloatText(value=self._Q20, min=0, max=100.0,
                                        step=0.5,
                                        layout=wi.Layout(width='60px'))
        wi.jslink((self._wQ2, 'value'), (self._tQ2, 'value'))

        self._bQ2 = wi.Button(description='Set',
                              layout=wi.Layout(width='50px'))
        self._bQ2.on_click(self._Q2_click)

        self._wT1 = wi.FloatSlider(value=self._T1_SP, min=20, max=60.0,
                                   step=0.5, description='T1 SP:',
                                   continuous_update=False,
                                   orientation='horizontal',
                                   readout=False, style=style,
                                   disabled=True,
                                   layout=wi.Layout(width='230px'))
        self._tT1 = wi.BoundedFloatText(value=self._T1_SP, min=20, max=60.0,
                                        step=0.5, disabled=True,
                                        layout=wi.Layout(width='60px'))
        wi.jslink((self._wT1, 'value'), (self._tT1, 'value'))

        self._bT1 = wi.Button(description='Set',
                              layout=wi.Layout(width='50px'),
                              disabled=True)
        self._bT1.on_click(self._T1_click)

        self._wT2 = wi.FloatSlider(value=self._T2_SP, min=20, max=60.0,
                                   step=0.5, description='T2 SP:',
                                   continuous_update=False,
                                   orientation='horizontal', disabled=True,
                                   readout=False, style=style,
                                   layout=wi.Layout(width='230px'))
        self._tT2 = wi.BoundedFloatText(value=self._T2_SP, min=20, max=60.0,
                                        step=0.5, disabled=True,
                                        layout=wi.Layout(width='60px'))
        wi.jslink((self._wT2, 'value'), (self._tT2, 'value'))

        self._bT2 = wi.Button(description='Set',
                              layout=wi.Layout(width='50px'), disabled=True)
        self._bT2.on_click(self._T2_click)

        #######################################################################
        #                                                       MODE SELECTION
        #######################################################################
        self._mode = wi.ToggleButtons(options=['Manual',
                                               'On-Off',
                                               'PID',
                                               'MPC'],
                                      style={'button_width': '100px'})
        self._mode.observe(self._mode_switch, names='value')

        #######################################################################
        #                                                          STOP THREAD
        #######################################################################
        self._b_stop = wi.Button(description='Stop', button_style='warning',
                                 icon='stop', layout=wi.Layout(width='100px',
                                                               height='32px'))
        self._b_stop.on_click(self._stop_click)

        #######################################################################
        #                                             START THREAD - OPEN LOOP
        #######################################################################
        self._b_play = wi.Button(description='Start', button_style='success',
                                 icon='play', layout=wi.Layout(width='100px',
                                                               height='32px'))
        self._b_play.on_click(self._play_click)

        # Join Buttons
        buttons = wi.HBox((self._b_play, h_space, self._b_stop,
                           wi.Label(value="", layout=wi.Layout(width='165px')),
                           self._mode))

        #######################################################################
        #                                                               LAYOUT
        #######################################################################
        Q1_Set = wi.HBox((h_space, self._wQ1, self._tQ1, self._bQ1),
                         layout=wi.Layout(border='solid 2px gray',
                                          width='360px'))
        T1_Set = wi.HBox((h_space, self._wT1, self._tT1, self._bT1),
                         layout=wi.Layout(border='solid 2px gray',
                                          width='360px'))
        T1_View = wi.HBox((h_space, self._PT1, self._LT1),
                          layout=wi.Layout(border='solid 2px gray',
                                           width='360px'))
        Q2_Set = wi.HBox((h_space, self._wQ2, self._tQ2, self._bQ2),
                         layout=wi.Layout(border='solid 2px gray',
                                          width='360px'))
        T2_Set = wi.HBox((h_space, self._wT2, self._tT2, self._bT2),
                         layout=wi.Layout(border='solid 2px gray',
                                          width='360px'))
        T2_View = wi.HBox((h_space, self._PT2, self._LT2),
                          layout=wi.Layout(border='solid 2px gray',
                                           width='360px'))

        co1 = wi.VBox((T1_Set, v_space, T1_View, v_space, Q1_Set))
        co2 = wi.VBox((T2_Set, v_space, T2_View, v_space, Q2_Set))
        co = wi.VBox((v_space, v_space, co1, v_space, v_space, v_space, co2))
        fig1x = wi.HBox((fig1, fig2))
        fig2x = wi.HBox((fig3, h_space, h_space, co))
        figy = wi.VBox((fig1x, v_space, fig2x),
                       layout=wi.Layout(border='solid 2px gray',
                                        width='800px'))

        #######################################################################
        #                                               APPLICATION TO DISPLAY
        #######################################################################
        self._gui = wi.VBox((buttons, v_space, figy))

        #######################################################################
        #                                                         CONFIGURATOR
        #######################################################################
        style = {'description_width': 'initial'}
        lay = wi.Layout(width='120px', margin='0 20px 0 0')

        #######################################################################
        #                                                      GENERAL OPTIONS
        #######################################################################
        self._conf11 = wi.HBox((
            wi.HTML(
                value='
Δt (s):
',
                layout=lay),
            wi.FloatSlider(value=4.0, min=1.0, max=10.0, step=0.5,
                           description='', style=style)))

        self._conf12 = wi.HBox((
            wi.HTML(value='
'
                          'Sleep time (s):
',
                    layout=lay),
            wi.FloatSlider(value=0.5, min=0.25, max=1.5, step=0.05,
                           description='', style=style)))

        but11 = wi.Button(description='Apply', icon='check',
                          layout=wi.Layout(width='100px', height='32px'))
        but11.on_click(self._conf_general)

        but12 = wi.Button(description='Reset', icon='refresh',
                          layout=wi.Layout(width='100px', height='32px'))
        but12.on_click(self._reset_general)

        conf13 = wi.HBox((but11, but12), layout=wi.Layout(margin='10px 0 0 0'))

        #######################################################################
        #                                                       ON-OFF OPTIONS
        #######################################################################
        lay5 = wi.Layout(width='90px', margin='0 10px 0 15px')

        conf20 = wi.Button(description='TEMPERATURE 01 / HEATER 01',
                           disabled=True,
                           layout=wi.Layout(width='320px', margin='0 0 0 0'))

        self._conf21 = wi.HBox((
            wi.HTML(value='
'
                          'Deadband (K):
',
                    layout=lay5),
            wi.FloatSlider(value=0.1, min=0.0, max=2.0, step=0.1,
                           description='', style=style,
                           layout=wi.Layout(width='200px'))),
            layout=wi.Layout(margin='5px 0 0 0')
        )

        box21 = wi.VBox((conf20, self._conf21),
                        layout=wi.Layout(border='solid 2px gray',
                                         width='325px',
                                         margin='10px 0 0 0px'))

        conf22 = wi.Button(description='TEMPERATURE 02 / HEATER 02',
                           disabled=True,
                           layout=wi.Layout(width='320px', margin='0 0 0 0'))

        self._conf23 = wi.HBox((
            wi.HTML(value='
'
                          'Deadband (K):
',
                    layout=lay5),
            wi.FloatSlider(value=0.1, min=0.0, max=2.0, step=0.1,
                           description='', style=style,
                           layout=wi.Layout(width='200px'))),
            layout=wi.Layout(margin='5px 0 0 0')
        )

        box22 = wi.VBox((conf22, self._conf23),
                        layout=wi.Layout(border='solid 2px gray',
                                         width='325px',
                                         margin='10px 0 0 10px'))

        but21 = wi.Button(description='Apply', icon='check',
                          layout=wi.Layout(width='100px', height='32px'))
        but21.on_click(self._conf_on_off)

        but22 = wi.Button(description='Reset', icon='refresh',
                          layout=wi.Layout(width='100px', height='32px'))
        but22.on_click(self._reset_on_off)

        conf24 = wi.HBox((but21, but22), layout=wi.Layout(margin='10px 0 0 0'))

        #######################################################################
        #                                                          PID OPTIONS
        #######################################################################
        lay4 = wi.Layout(width='100px', margin='0 10px 0 15px')

        conf30 = wi.Button(description='TEMPERATURE 01 / HEATER 01',
                           disabled=True,
                           layout=wi.Layout(width='330px', margin='0 0 0 0'))

        self._conf31 = wi.HBox((
            wi.HTML(value='
'
                          'Kc (K/%Heater):
',
                    layout=lay4),
            wi.FloatSlider(value=10.0, min=0.0, max=20.0, step=0.5,
                           description='', style=style,
                           layout=wi.Layout(width='200px'))),
            layout=wi.Layout(margin='5px 0 0 0')
        )

        self._conf32 = wi.HBox((
            wi.HTML(value='
tauI (s):
',
                    layout=lay4),
            wi.FloatSlider(value=50.0, min=0.0, max=200.0, step=1.0,
                           description='', style=style,
                           layout=wi.Layout(width='200px'))))

        self._conf33 = wi.HBox((
            wi.HTML(value='
tauD (s):
',
                    layout=lay4),
            wi.FloatSlider(value=1.0, min=0.0, max=10.0, step=0.5,
                           description='', style=style,
                           layout=wi.Layout(width='200px'))))

        box31 = wi.VBox((conf30, self._conf31, self._conf32, self._conf33),
                        layout=wi.Layout(border='solid 2px gray',
                                         width='335px',
                                         margin='10px 0 0 0px'))

        conf34 = wi.Button(description='TEMPERATURE 02 / HEATER 02',
                           disabled=True,
                           layout=wi.Layout(width='330px', margin='0 0 0 0'))

        self._conf35 = wi.HBox((
            wi.HTML(value='
'
                          'Kc (K/%Heater):
',
                    layout=lay4),
            wi.FloatSlider(value=10.0, min=0.0, max=20.0, step=0.5,
                           description='', style=style,
                           layout=wi.Layout(width='200px'))),
            layout=wi.Layout(margin='5px 0 0 0')
        )

        self._conf36 = wi.HBox((
            wi.HTML(value='
tauI (s):
',
                    layout=lay4),
            wi.FloatSlider(value=50.0, min=0.0, max=200.0, step=1.0,
                           description='', style=style,
                           layout=wi.Layout(width='200px'))))

        self._conf37 = wi.HBox((
            wi.HTML(value='
tauD (s):
',
                    layout=lay4),
            wi.FloatSlider(value=1.0, min=0.0, max=10.0, step=0.5,
                           description='', style=style,
                           layout=wi.Layout(width='200px'))))

        box32 = wi.VBox((conf34, self._conf35, self._conf36, self._conf37),
                        layout=wi.Layout(border='solid 2px gray',
                                         width='335px',
                                         margin='10px 0 0 10px'))

        but31 = wi.Button(description='Apply', icon='check',
                          layout=wi.Layout(width='100px', height='32px'))
        but31.on_click(self._conf_pid)

        but32 = wi.Button(description='Reset', icon='refresh',
                          layout=wi.Layout(width='100px', height='32px'))
        but32.on_click(self._reset_pid)

        conf38 = wi.HBox((but31, but32), layout=wi.Layout(margin='10px 0 0 0'))

        #######################################################################
        #                                                          MPC OPTIONS
        #######################################################################
        lay1 = wi.Layout(width='60px', margin='0 10px 0 25px')
        lay2 = wi.Layout(width='95px', margin='0 20px 0 10px')
        lay3 = wi.Layout(width='55px', margin='0 20px 0 10px')

        self._conf40 = wi.HBox((
            wi.HTML(value='
SOLVER:
',
                    layout=lay1),
            wi.Dropdown(options=['1 - APOPT', '2 - BPOPT', '3 - IPOPT'],
                        value='1 - APOPT',
                        layout=wi.Layout(width='100px')),
            wi.HTML(value='
CVTYPE:
',
                    layout=lay1),
            wi.Dropdown(options=['1 - Deadband', '2 - Trajectory'],
                        value='1 - Deadband',
                        layout=wi.Layout(width='130px'))),
            layout=wi.Layout(margin='5px 0 0 0')
        )

        conf41 = wi.Button(description='TEMPERATURE 01',
                           disabled=True,
                           layout=wi.Layout(width='360px', margin='0 0 0 0'))

        self._conf42 = wi.HBox((
            wi.HTML(value='
'
                          'Deadband (K):
',
                    layout=lay2),
            wi.FloatSlider(value=0.1, min=0.1, max=1.0, step=0.1,
                           description='', style=style,
                           layout=wi.Layout(width='250px'))),
            layout=wi.Layout(margin='5px 0 0 0')
        )

        self._conf43 = wi.HBox((
            wi.HTML(value='
TAU:
',
                    layout=lay2),
            wi.FloatSlider(value=10.0, min=1.0, max=100.0, step=1.0,
                           description='', style=style,
                           layout=wi.Layout(width='250px'))))

        box41 = wi.VBox((conf41, self._conf42, self._conf43),
                        layout=wi.Layout(border='solid 2px gray',
                                         width='365px',
                                         margin='10px 0 0 0'))

        conf44 = wi.Button(description='TEMPERATURE 02',
                           disabled=True,
                           layout=wi.Layout(width='360px', margin='0 0 0 0'))

        self._conf45 = wi.HBox((
            wi.HTML(value='
'
                          'Deadband (K):
',
                    layout=lay2),
            wi.FloatSlider(value=0.1, min=0.1, max=1.0, step=0.1,
                           description='', style=style,
                           layout=wi.Layout(width='250px'))),
            layout=wi.Layout(margin='5px 0 0 0')
        )

        self._conf46 = wi.HBox((
            wi.HTML(value='
TAU:
',
                    layout=lay2),
            wi.FloatSlider(value=10.0, min=1.0, max=100.0, step=1.0,
                           description='', style=style,
                           layout=wi.Layout(width='250px'))))

        box42 = wi.VBox((conf44, self._conf45, self._conf46),
                        layout=wi.Layout(border='solid 2px gray',
                                         width='365px',
                                         margin='10px 0 0 0'))

        conf47 = wi.Button(description='HEATER 01',
                           disabled=True,
                           layout=wi.Layout(width='320px', margin='0 0 0 0'))

        self._conf48 = wi.HBox((
            wi.HTML(value='
DMAX:
',
                    layout=lay3),
            wi.FloatSlider(value=30.0, min=1.0, max=100.0, step=0.5,
                           description='', style=style,
                           layout=wi.Layout(width='250px'))),
            layout=wi.Layout(margin='5px 0 0 0')
        )

        self._conf49 = wi.HBox((
            wi.HTML(value='
DCOST:
',
                    layout=lay3),
            wi.FloatSlider(value=1.0, min=0.0, max=10.0, step=0.1,
                           description='', style=style,
                           layout=wi.Layout(width='250px'))))

        box43 = wi.VBox((conf47, self._conf48, self._conf49),
                        layout=wi.Layout(border='solid 2px gray',
                                         width='325px',
                                         margin='10px 0 0 10px'))

        conf410 = wi.Button(description='HEATER 02',
                            disabled=True,
                            layout=wi.Layout(width='320px', margin='0 0 0 0'))

        self._conf411 = wi.HBox((
            wi.HTML(value='
DMAX:
',
                    layout=lay3),
            wi.FloatSlider(value=30.0, min=1.0, max=100.0, step=0.5,
                           description='', style=style,
                           layout=wi.Layout(width='250px'))),
            layout=wi.Layout(margin='5px 0 0 0')
        )

        self._conf412 = wi.HBox((
            wi.HTML(value='
DCOST:
',
                    layout=lay3),
            wi.FloatSlider(value=1.0, min=0.0, max=10.0, step=0.1,
                           description='', style=style,
                           layout=wi.Layout(width='250px'))))

        box44 = wi.VBox((conf410, self._conf411, self._conf412),
                        layout=wi.Layout(border='solid 2px gray',
                                         width='325px',
                                         margin='10px 0 0 10px'))

        but41 = wi.Button(description='Apply', icon='check',
                          layout=wi.Layout(width='100px', height='32px'))
        but41.on_click(self._conf_mpc)
        but42 = wi.Button(description='Reset', icon='refresh',
                          layout=wi.Layout(width='100px', height='32px'))
        but42.on_click(self._reset_mpc)
        conf413 = wi.HBox((but41, but42),
                          layout=wi.Layout(margin='10px 0 0 0'))

        #######################################################################
        #                                                  CONFIGURATOR LAYOUT
        #######################################################################
        tab = wi.Tab([wi.VBox((self._conf11, self._conf12,
                               wi.Label(layout=wi.Layout(height='206px')),
                               conf13)),
                      wi.VBox((wi.HBox((box21, box22)),
                               wi.Label(layout=wi.Layout(height='191px')),
                               conf24)),
                      wi.VBox((wi.HBox((box31, box32)),
                               wi.Label(layout=wi.Layout(height='127px')),
                               conf38)),
                      wi.VBox((self._conf40,
                               wi.HBox((box41, box43)),
                               wi.HBox((box42, box44)),
                               wi.Label(layout=wi.Layout(height='11px')),
                               conf413))],
                     layout=wi.Layout(width='800px', height='380px'))
        tab.set_title(0, 'General Options')
        tab.set_title(1, 'On-Off Options')
        tab.set_title(2, 'PID Options')
        tab.set_title(3, 'MPC Options')

        #######################################################################
        #                                                 DISPLAY CONFIGURATOR
        #######################################################################
        self._conf = tab

    def app(self):
        display(self._gui)

    def config(self):
        display(self._conf)

    def _conf_general(self, b):
        self._delta_t = self._conf11.children[1].value
        self._maxtime = int(500/self._delta_t)

        self._sleep = self._conf12.children[1].value

    def _reset_general(self, b):
        self._conf11.children[1].value = 4.0
        self._delta_t = self._conf11.children[1].value
        self._maxtime = int(500/self._delta_t)

        self._conf12.children[1].value = 0.5
        self._sleep = self._conf12.children[1].value

    def _conf_on_off(self, b):
        self._q1_dt_on_off = self._conf21.children[1].value

        self._q2_dt_on_off = self._conf23.children[1].value

    def _reset_on_off(self, b):
        self._conf21.children[1].value = 0.1
        self._q1_dt_on_off = self._conf21.children[1].value

        self._conf23.children[1].value = 0.1
        self._q2_dt_on_off = self._conf23.children[1].value

    def _conf_pid(self, b):
        self._pid1_gain = self._conf31.children[1].value
        self._pid1_reset = self._conf32.children[1].value
        self._pid1_rate = self._conf33.children[1].value

        self._pid2_gain = self._conf35.children[1].value
        self._pid2_reset = self._conf36.children[1].value
        self._pid2_rate = self._conf37.children[1].value

    def _reset_pid(self, b):
        self._conf31.children[1].value = 10.0  # gain
        self._conf32.children[1].value = 50.0  # reset
        self._conf33.children[1].value = 1.0   # rate
        self._pid1_gain = self._conf31.children[1].value
        self._pid1_reset = self._conf32.children[1].value
        self._pid1_rate = self._conf33.children[1].value

        self._conf35.children[1].value = 10.0  # gain
        self._conf36.children[1].value = 50.0  # reset
        self._conf37.children[1].value = 1.0   # rate
        self._pid2_gain = self._conf35.children[1].value
        self._pid2_reset = self._conf36.children[1].value
        self._pid2_rate = self._conf37.children[1].valu

    def _conf_mpc(self, b):
        self._SOLVER = self._conf40.children[1].value
        self._CVTYPE = self._conf40.children[3].value

        self._T1_dt = self._conf42.children[1].value
        self._T1_tau = self._conf43.children[1].value
        self._T2_dt = self._conf45.children[1].value
        self._T2_tau = self._conf46.children[1].value

        self._Q1_DMAX = self._conf48.children[1].value
        self._Q1_DCOST = self._conf49.children[1].value
        self._Q2_DMAX = self._conf411.children[1].value
        self._Q2_DCOST = self._conf412.children[1].value

    def _reset_mpc(self, b):
        self._conf40.children[1].value = '1 - APOPT'
        self._conf40.children[3].value = '1 - Deadband'

        self._conf42.children[1].value = 0.1
        self._conf43.children[1].value = 30.
        self._conf45.children[1].value = 0.1
        self._conf46.children[1].value = 30.

        self._conf48.children[1].value = 30.
        self._conf49.children[1].value = 1.
        self._conf411.children[1].value = 30.
        self._conf412.children[1].value = 1.

        self._SOLVER = self._conf40.children[1].value
        self._CVTYPE = self._conf40.children[3].value

        self._T1_dt = self._conf42.children[1].value
        self._T1_tau = self._conf43.children[1].value
        self._T2_dt = self._conf45.children[1].value
        self._T2_tau = self._conf46.children[1].value

        self._Q1_DMAX = self._conf48.children[1].value
        self._Q1_DCOST = self._conf49.children[1].value
        self._Q2_DMAX = self._conf411.children[1].value
        self._Q2_DCOST = self._conf412.children[1].value

    def _Q1_click(self, b):
        self._Q10 = self._wQ1.value

    def _Q2_click(self, b):
        self._Q20 = self._wQ2.value

    def _T1_click(self, b):
        self._T1_SP = self._wT1.value

    def _T2_click(self, b):
        self._T2_SP = self._wT2.value

    def _stop_click(self, b):
        self._flag = False
        self._mode.disabled = False

    def _play_click(self, b):
        if not self._flag:
            if self._mode.value == "Manual":
                self._flag = True
                self._mode.disabled = True
                thread = threading.Thread(target=self._work_man)
                thread.start()
            elif self._mode.value == "On-Off":
                self._flag = True
                self._mode.disabled = True
                thread = threading.Thread(target=self._work_on_off)
                thread.start()
            elif self._mode.value == "PID":
                self._flag = True
                self._mode.disabled = True
                thread = threading.Thread(target=self._work_pid)
                thread.start()
            elif self._mode.value == "MPC":
                self._flag = True
                self._mode.disabled = True
                thread = threading.Thread(target=self._work_mpc)
                thread.start()

    def _mode_switch(self, value):
        # Reinitialize parameters
        self._T1_SP = 30
        self._T2_SP = 30
        self._Q10 = 0
        self._Q20 = 0

        # Reset figures
        self._T1_meas.x = []
        self._T1_meas.y = []
        self._T2_meas.x = []
        self._T2_meas.y = []

        self._T1_set_point.x = []
        self._T1_set_point.y = []
        self._T2_set_point.x = []
        self._T2_set_point.y = []

        self._u1.x = []
        self._u1.y = []
        self._u2.x = []
        self._u2.y = []

        # Reset controls
        self._PT1.value = self._Tc0[0]-273.15
        self._PT2.value = self._Tc0[1]-273.15
        self._wT1.value = self._T1_SP
        self._wT2.value = self._T2_SP
        self._wQ1.value = self._Q10
        self._wQ2.value = self._Q20

        if value['new'] == "Manual":
            self._wQ1.disabled = False
            self._tQ1.disabled = False
            self._bQ1.disabled = False
            self._wT1.disabled = True
            self._tT1.disabled = True
            self._bT1.disabled = True

            self._wQ2.disabled = False
            self._tQ2.disabled = False
            self._bQ2.disabled = False
            self._wT2.disabled = True
            self._tT2.disabled = True
            self._bT2.disabled = True

        else:
            self._wQ1.disabled = True
            self._tQ1.disabled = True
            self._bQ1.disabled = True
            self._wT1.disabled = False
            self._tT1.disabled = False
            self._bT1.disabled = False

            self._wQ2.disabled = True
            self._tQ2.disabled = True
            self._bQ2.disabled = True
            self._wT2.disabled = False
            self._tT2.disabled = False
            self._bT2.disabled = False

    ###########################################################################
    #                                                           PID CONTROLLER
    ###########################################################################

    # inputs -----------------------------------
    # sp = setpoint
    # pv = current temperature
    # pv_last = prior temperature
    # ierr = integral error
    # dt = time increment between measurements

    # outputs ----------------------------------
    # op = output of the PID controller
    # I = integral contribution

    def _PID(self, sp, pv, pv_last, ierr, dt, Kc=10.0, tauI=50.0, tauD=1.0):
        # Default Parameters
        # Kc   = 10.0 # K/%Heater
        # tauI = 50.0 # sec
        # tauD = 1.0  # sec

        # Parameters in terms of PID coefficients
        KP = Kc
        if tauI == 0:
            KI = 1e5
        else:
            KI = Kc/tauI
        KD = Kc*tauD

        # ubias for controller (initial heater)
        op0 = 0

        # upper and lower bounds on heater level
        ophi = 100
        oplo = 0

        # calculate the error
        error = sp-pv

        # calculate the integral error
        ierr = ierr + KI * error * dt

        # calculate the measurement derivative
        dpv = (pv - pv_last) / dt

        # calculate the PID output
        P = KP * error
        I = ierr
        D = -KD * dpv
        op = op0 + P + I + D

        # implement anti-reset windup
        if op < oplo or op > ophi:
            I = I - KI * error * dt
            # clip output
            op = max(oplo, min(ophi, op))

        # return the controller output and PID terms
        return [op, I]

    ###########################################################################
    #                                                                      MPC
    ###########################################################################
    def _MPC(self):

        m = GEKKO(remote=False)

        # 60 second time horizon, 4 sec cycle time, non-uniform
        m.time = [0, 4, 8, 12, 15, 20, 25, 30, 35, 40, 50, 60, 70, 80, 90]

        # Parameters
        m.U = m.FV(value=10)
        m.tau = m.FV(value=5)
        m.alpha1 = m.FV(value=0.01)    # W / % heater
        m.alpha2 = m.FV(value=0.0075)  # W / % heater

        # Manipulated variables
        m.Q1 = m.MV(value=0)
        m.Q1.STATUS = 1   # use to control temperature
        m.Q1.FSTATUS = 0  # no feedback measurement
        m.Q1.LOWER = 0.0
        m.Q1.UPPER = 100.0
        m.Q1.DMAX = 20.0
        m.Q1.COST = 0.0
        m.Q1.DCOST = 2.0

        m.Q2 = m.MV(value=0)
        m.Q2.STATUS = 1   # use to control temperature
        m.Q2.FSTATUS = 0  # no feedback measurement
        m.Q2.LOWER = 0.0
        m.Q2.UPPER = 100.0
        m.Q2.DMAX = 20.0
        m.Q2.COST = 0.0
        m.Q2.DCOST = 2.0

        # Controlled variable
        m.TC1 = m.CV(value=22)
        m.TC1.STATUS = 1     # minimize error with setpoint range
        m.TC1.FSTATUS = 1    # receive measurement
        m.TC1.TR_INIT = 1    # reference trajectory
        m.TC1.TAU = 10       # time constant for response

        # Controlled variable
        m.TC2 = m.CV(value=22)
        m.TC2.STATUS = 1     # minimize error with setpoint range
        m.TC2.FSTATUS = 1    # receive measurement
        m.TC2.TR_INIT = 1    # reference trajectory
        m.TC2.TAU = 10       # time constant for response

        # State variables
        m.TH1 = m.SV(value=22)
        m.TH2 = m.SV(value=22)

        m.Ta = m.Param(value=23.0+273.15)     # K
        m.mass = m.Param(value=4.0/1000.0)    # kg
        m.Cp = m.Param(value=0.5*1000.0)      # J/kg-K
        m.A = m.Param(value=10.0/100.0**2)    # Area not between heaters in m^2
        m.As = m.Param(value=2.0/100.0**2)    # Area between heaters in m^2
        m.eps = m.Param(value=0.9)            # Emissivity
        m.sigma = m.Const(5.67e-8)            # Stefan-Boltzmann

        # Heater temperatures
        m.T1i = m.Intermediate(m.TH1+273.15)
        m.T2i = m.Intermediate(m.TH2+273.15)

        # Heat transfer between two heaters
        m.Q_C12 = m.Intermediate(m.U*m.As*(m.T2i-m.T1i))  # Conv
        m.Q_R12 = m.Intermediate(m.eps*m.sigma*m.As*(m.T2i**4-m.T1i**4))  # Rad

        # Semi-fundamental correlations (energy balances)
        m.Equation(m.TH1.dt() == (1.0/(m.mass*m.Cp)) *
                                 (m.U*m.A*(m.Ta-m.T1i) +
                                  m.eps * m.sigma * m.A *
                                  (m.Ta**4 - m.T1i**4) + m.Q_C12 +
                                  m.Q_R12 + m.alpha1*m.Q1))

        m.Equation(m.TH2.dt() == (1.0/(m.mass*m.Cp)) *
                                 (m.U*m.A*(m.Ta-m.T2i) +
                                  m.eps * m.sigma * m.A *
                                  (m.Ta**4 - m.T2i**4) - m.Q_C12 -
                                  m.Q_R12 + m.alpha2*m.Q2))

        # Empirical correlations (lag equations to emulate conduction)
        m.Equation(m.tau * m.TC1.dt() == -m.TC1 + m.TH1)
        m.Equation(m.tau * m.TC2.dt() == -m.TC2 + m.TH2)

        # Global Options
        m.options.IMODE = 6    # MPC
        m.options.CV_TYPE = 1  # Objective type
        m.options.NODES = 3    # Collocation nodes
        m.options.SOLVER = 3   # 1=APOPT, 3=IPOPT

        return m

    ###########################################################################
    #                                           THREADING FUNCTION - OPEN LOOP
    ###########################################################################
    def _work_man(self):
        try:
            a = TCLab()
        except:
            a.close()
            a = TCLab()

        # Parater to start each cycle
        self._Tc0 = np.array([
            a.T1 + 273.15,
            a.T2 + 273.15
        ])

        # arrays to store data
        t = np.array([])
        Q1 = np.array([])
        Q2 = np.array([])
        T = np.array([[]]).reshape((0, 2))

        t = np.append(t, np.array([0]), axis=0)
        T = np.append(T, self._Tc0.reshape((1, 2)), axis=0)
        Q1 = np.append(Q1, np.array([self._Q10]), axis=0)
        Q2 = np.append(Q2, np.array([self._Q20]), axis=0)

        # Main Loop
        start_time = time.time()
        prev_time = start_time

        while self._flag:

            # Sleep time
            sleep_max = self._delta_t
            sleep = sleep_max - (time.time() - prev_time)
            if sleep >= 0.01:
                time.sleep(sleep-0.01)
            else:
                time.sleep(0.01)

            # Read temperatures in Celsius
            self._Tc0 = np.array([
                a.T1 + 273.15,
                a.T2 + 273.15
            ])

            # Write new heater values (0-100)
            a.Q1(self._Q10)
            a.Q2(self._Q20)

            if len(t) >= self._maxtime:
                t = np.delete(t, 0, 0)
                T = np.delete(T, 0, 0)
                Q1 = np.delete(Q1, 0, 0)
                Q2 = np.delete(Q2, 0, 0)

            # Record time and change in time
            tm = time.time()
            prev_time = tm
            tm = tm - start_time

            t = np.append(t, np.array([tm]), axis=0)
            T = np.append(T, self._Tc0.reshape((1, 2)), axis=0)
            Q1 = np.append(Q1, np.array([self._Q10]), axis=0)
            Q2 = np.append(Q2, np.array([self._Q20]), axis=0)

            self._T1_meas.x = t/60
            self._T1_meas.y = T[:, 0] - 273.15
            self._PT1.value = np.round(T[-1, 0]-273.15, 1)
            self._wT1.value = np.round(T[-1, 0]-273.15, 1)

            self._T2_meas.x = t/60
            self._T2_meas.y = T[:, 1] - 273.15
            self._PT2.value = np.round(T[-1, 1]-273.15, 1)
            self._wT2.value = np.round(T[-1, 1]-273.15, 1)

            self._u1.x = t/60
            self._u1.y = Q1

            self._u2.x = t/60
            self._u2.y = Q2

            time.sleep(self._sleep)

        a.Q1(0)
        a.Q2(0)
        a.close()

    ###########################################################################
    #                                              THREADING FUNCTION - ON-OFF
    ###########################################################################
    def _work_on_off(self):
        try:
            a = TCLab()
        except:
            a.close()
            a = TCLab()

        # Parater to start each cycle
        self._Tc0 = np.array([
            a.T1 + 273.15,
            a.T2 + 273.15
        ])

        # arrays to store data
        t = np.array([])
        Q1 = np.array([])
        Q2 = np.array([])
        T = np.array([[]]).reshape((0, 2))
        SP_T1 = np.array([])
        SP_T2 = np.array([])

        t = np.append(t, np.array([0]), axis=0)
        T = np.append(T, self._Tc0.reshape((1, 2)), axis=0)
        Q1 = np.append(Q1, np.array([self._Q10]), axis=0)
        Q2 = np.append(Q2, np.array([self._Q20]), axis=0)
        SP_T1 = np.append(SP_T1, np.array([self._T1_SP]), axis=0)
        SP_T2 = np.append(SP_T2, np.array([self._T2_SP]), axis=0)

        # Main Loop
        start_time = time.time()
        prev_time = start_time

        while self._flag:

            # Sleep time
            sleep_max = self._delta_t
            sleep = sleep_max - (time.time() - prev_time)
            if sleep >= 0.01:
                time.sleep(sleep-0.01)
            else:
                time.sleep(0.01)

            # Read temperatures in Celsius
            self._Tc0 = np.array([
                a.T1 + 273.15,
                a.T2 + 273.15
            ])

            # apply ON/OFF controller
            # heater 1
            if (self._Tc0[0]-273.15) < (self._T1_SP - self._q1_dt_on_off):
                self._Q10 = 100.0
            elif (self._Tc0[0]-273.15) > (self._T1_SP + self._q1_dt_on_off):
                self._Q10 = 0.0
            # heater 2
            if (self._Tc0[1]-273.15) < (self._T2_SP - self._q2_dt_on_off):
                self._Q20 = 100.0
            elif (self._Tc0[1]-273.15) > (self._T2_SP + self._q2_dt_on_off):
                self._Q20 = 0.0

            # Write new heater values (0-100)
            a.Q1(self._Q10)
            a.Q2(self._Q20)

            if len(t) >= self._maxtime:
                t = np.delete(t, 0, 0)
                T = np.delete(T, 0, 0)
                Q1 = np.delete(Q1, 0, 0)
                Q2 = np.delete(Q2, 0, 0)
                SP_T1 = np.delete(SP_T1, 0, 0)
                SP_T2 = np.delete(SP_T2, 0, 0)

            # Record time and change in time
            tm = time.time()
            prev_time = tm
            tm = tm - start_time

            t = np.append(t, np.array([tm]), axis=0)
            T = np.append(T, self._Tc0.reshape((1, 2)), axis=0)
            Q1 = np.append(Q1, np.array([self._Q10]), axis=0)
            Q2 = np.append(Q2, np.array([self._Q20]), axis=0)
            SP_T1 = np.append(SP_T1, np.array([self._T1_SP]), axis=0)
            SP_T2 = np.append(SP_T2, np.array([self._T2_SP]), axis=0)

            self._T1_meas.x = t/60
            self._T1_meas.y = T[:, 0] - 273.15
            self._PT1.value = np.round(T[-1, 0]-273.15, 1)

            self._T1_set_point.x = t/60
            self._T1_set_point.y = SP_T1

            self._T2_meas.x = t/60
            self._T2_meas.y = T[:, 1] - 273.15
            self._PT2.value = np.round(T[-1, 1]-273.15, 1)

            self._T2_set_point.x = t/60
            self._T2_set_point.y = SP_T2

            self._u1.x = t/60
            self._u1.y = Q1
            self._wQ1.value = np.round(Q1[-1], 1)

            self._u2.x = t/60
            self._u2.y = Q2
            self._wQ2.value = np.round(Q2[-1], 1)

            time.sleep(self._sleep)

        a.Q1(0)
        a.Q2(0)
        a.close()

    ###########################################################################
    #                                                 THREADING FUNCTION - PID
    ###########################################################################
    def _work_pid(self):
        try:
            a = TCLab()
        except:
            a.close()
            a = TCLab()

        # Parater to start each cycle
        self._Tc0 = np.array([
            a.T1 + 273.15,
            a.T2 + 273.15
        ])

        # arrays to store data
        t = np.array([])
        Q1 = np.array([])
        Q2 = np.array([])
        T = np.array([[]]).reshape((0, 2))
        SP_T1 = np.array([])
        SP_T2 = np.array([])

        t = np.append(t, np.array([0]), axis=0)
        T = np.append(T, self._Tc0.reshape((1, 2)), axis=0)
        Q1 = np.append(Q1, np.array([self._Q10]), axis=0)
        Q2 = np.append(Q2, np.array([self._Q20]), axis=0)
        SP_T1 = np.append(SP_T1, np.array([self._T1_SP]), axis=0)
        SP_T2 = np.append(SP_T2, np.array([self._T2_SP]), axis=0)

        # Integral error
        ierr1 = 0.0
        ierr2 = 0.0

        # Main Loop
        start_time = time.time()
        prev_time = start_time

        while self._flag:

            # Sleep time
            sleep_max = self._delta_t
            sleep = sleep_max - (time.time() - prev_time)
            if sleep >= 0.01:
                time.sleep(sleep-0.01)
            else:
                time.sleep(0.01)

            # Read temperatures in Celsius
            self._Tc0 = np.array([
                a.T1 + 273.15,
                a.T2 + 273.15
            ])

            if len(t) >= self._maxtime:
                t = np.delete(t, 0, 0)
                T = np.delete(T, 0, 0)
                Q1 = np.delete(Q1, 0, 0)
                Q2 = np.delete(Q2, 0, 0)
                SP_T1 = np.delete(SP_T1, 0, 0)
                SP_T2 = np.delete(SP_T2, 0, 0)

            # Record time and change in time
            tm = time.time()
            prev_time = tm
            tm = tm - start_time

            t = np.append(t, np.array([tm]), axis=0)
            T = np.append(T, self._Tc0.reshape((1, 2)), axis=0)
            Q1 = np.append(Q1, np.array([self._Q10]), axis=0)
            Q2 = np.append(Q2, np.array([self._Q20]), axis=0)
            SP_T1 = np.append(SP_T1, np.array([self._T1_SP]), axis=0)
            SP_T2 = np.append(SP_T2, np.array([self._T2_SP]), axis=0)

            # Calculate PID output
            [self._Q10, ierr1] = self._PID(
                self._T1_SP, T[-1, 0]-273.15, T[-2, 0]-273.15, ierr1,
                self._delta_t, self._pid1_gain, self._pid1_reset,
                self._pid1_rate)
            [self._Q20, ierr2] = self._PID(
                self._T2_SP, T[-1, 1]-273.15, T[-2, 1]-273.15, ierr2,
                self._delta_t, self._pid2_gain, self._pid2_reset,
                self._pid2_rate)

            # Write new heater values (0-100)
            a.Q1(self._Q10)
            a.Q2(self._Q20)

            self._T1_meas.x = t/60
            self._T1_meas.y = T[:, 0] - 273.15
            self._PT1.value = np.round(T[-1, 0]-273.15, 1)

            self._T1_set_point.x = t/60
            self._T1_set_point.y = SP_T1

            self._T2_meas.x = t/60
            self._T2_meas.y = T[:, 1] - 273.15
            self._PT2.value = np.round(T[-1, 1]-273.15, 1)

            self._T2_set_point.x = t/60
            self._T2_set_point.y = SP_T2

            self._u1.x = t/60
            self._u1.y = Q1
            self._wQ1.value = np.round(Q1[-1], 1)

            self._u2.x = t/60
            self._u2.y = Q2
            self._wQ2.value = np.round(Q2[-1], 1)

            time.sleep(self._sleep)

        a.Q1(0)
        a.Q2(0)
        a.close()

    ###########################################################################
    #                                                 THREADING FUNCTION - MPC
    ###########################################################################
    def _work_mpc(self):
        try:
            a = TCLab()
        except:
            a.close()
            a = TCLab()

        # Parater to start each cycle
        self._Tc0 = np.array([
            a.T1 + 273.15,
            a.T2 + 273.15
        ])

        # arrays to store data
        t = np.array([])
        Q1 = np.array([])
        Q2 = np.array([])
        T = np.array([[]]).reshape((0, 2))
        SP_T1 = np.array([])
        SP_T2 = np.array([])

        t = np.append(t, np.array([0]), axis=0)
        T = np.append(T, self._Tc0.reshape((1, 2)), axis=0)
        Q1 = np.append(Q1, np.array([self._Q10]), axis=0)
        Q2 = np.append(Q2, np.array([self._Q20]), axis=0)
        SP_T1 = np.append(SP_T1, np.array([self._T1_SP]), axis=0)
        SP_T2 = np.append(SP_T2, np.array([self._T2_SP]), axis=0)

        # Create MPC object
        m = self._MPC()

        # Main Loop
        start_time = time.time()
        prev_time = start_time

        while self._flag:

            # Sleep time
            sleep_max = self._delta_t
            sleep = sleep_max - (time.time() - prev_time)
            if sleep >= 0.01:
                time.sleep(sleep-0.01)
            else:
                time.sleep(0.01)

            # Read temperatures in Celsius
            self._Tc0 = np.array([
                a.T1 + 273.15,
                a.T2 + 273.15
            ])

            if len(t) >= self._maxtime:
                t = np.delete(t, 0, 0)
                T = np.delete(T, 0, 0)
                Q1 = np.delete(Q1, 0, 0)
                Q2 = np.delete(Q2, 0, 0)
                SP_T1 = np.delete(SP_T1, 0, 0)
                SP_T2 = np.delete(SP_T2, 0, 0)

            # Record time and change in time
            tm = time.time()
            prev_time = tm
            tm = tm - start_time

            t = np.append(t, np.array([tm]), axis=0)
            T = np.append(T, self._Tc0.reshape((1, 2)), axis=0)
            Q1 = np.append(Q1, np.array([self._Q10]), axis=0)
            Q2 = np.append(Q2, np.array([self._Q20]), axis=0)
            SP_T1 = np.append(SP_T1, np.array([self._T1_SP]), axis=0)
            SP_T2 = np.append(SP_T2, np.array([self._T2_SP]), axis=0)

            # Change SOLVER
            if self._SOLVER == '1 - APOPT':
                m.options.SOLVER = 1
            elif self._SOLVER == '2 - BPOPT':
                m.options.SOLVER = 2
            else:
                m.options.SOLVER = 3

            # Change CVTYPE
            if self._CVTYPE == '1 - Deadband':
                m.options.CV_TYPE = 1
            else:
                m.options.CV_TYPE = 2

            # Add measurements to the MPC
            m.TC1.MEAS = self._Tc0[0] - 273.15
            m.TC2.MEAS = self._Tc0[1] - 273.15

            # Update Parameters
            m.TC1.TAU = self._T1_tau
            m.TC2.TAU = self._T2_tau

            m.Q1.DMAX = self._Q1_DMAX
            m.Q1.DCOST = self._Q1_DCOST
            m.Q2.DMAX = self._Q2_DMAX
            m.Q2.DCOST = self._Q2_DCOST

            # Update prediction horizon
            DT = self._delta_t
            m.time = [
                0,
                DT,
                DT*2,
                DT*3,
                DT*4,
                DT*5,
                DT*6,
                DT*7,
                DT*8,
                DT*10,
                DT*12,
                DT*15,
                DT*18,
                DT*20,
                DT*25]

            if m.options.CV_TYPE == 1:
                # Input setpoint with deadband +/- DT
                DT1 = self._T1_dt
                m.TC1.SPHI = self._T1_SP + DT1
                m.TC1.SPLO = self._T1_SP - DT1

                DT2 = self._T2_dt
                m.TC2.SPHI = self._T2_SP + DT2
                m.TC2.SPLO = self._T2_SP - DT2
            else:
                m.TC1.SP = self._T1_SP
                m.TC2.SP = self._T2_SP

            try:
                # Solve MPC
                m.solve(disp=False)
                # Check if successful solution
                if (m.options.APPSTATUS == 1):
                    # retrieve new value
                    self._Q10 = m.Q1.NEWVAL
                    self._Q20 = m.Q2.NEWVAL
            except:
                # Keep previous value
                pass

            # Write new heater values (0-100)
            a.Q1(self._Q10)
            a.Q2(self._Q20)

            self._T1_meas.x = t/60
            self._T1_meas.y = T[:, 0] - 273.15
            self._PT1.value = np.round(T[-1, 0]-273.15, 1)

            self._T1_set_point.x = t/60
            self._T1_set_point.y = SP_T1

            self._T2_meas.x = t/60
            self._T2_meas.y = T[:, 1] - 273.15
            self._PT2.value = np.round(T[-1, 1]-273.15, 1)

            self._T2_set_point.x = t/60
            self._T2_set_point.y = SP_T2

            self._u1.x = t/60
            self._u1.y = Q1
            self._wQ1.value = np.round(Q1[-1], 1)

            self._u2.x = t/60
            self._u2.y = Q2
            self._wQ2.value = np.round(Q2[-1], 1)

            time.sleep(self._sleep)

        a.Q1(0)
        a.Q2(0)
        a.close()


import networkx as nx

untypedkami = nx.DiGraph()
untypedkami.add_nodes_from(
    [
        "agent",
        "region",
        "residue",
        "locus",
        "state",
        "mod",
        "syn",
        "deg",
        "bnd",
        "brk",
        "is_bnd",
        "is_free",
    ]
)

untypedkami.add_edges_from(
    [
        ("region", "agent"),
        ("residue", "agent"),
        ("residue", "region"),
        ("state", "agent"),
        ("syn", "agent"),
        ("deg", "agent"),
        ("state", "region"),
        ("state", "residue"),
        ("locus", "agent"),
        ("locus", "region"),
        ("mod", "state"),
        ("locus", "bnd"),
        ("locus", "brk"),
        ("locus", "is_bnd"),
        ("locus", "is_free"),
        ("agent", "mod")
    ]
)

untyped_base_kami = nx.DiGraph()
untyped_base_kami.add_nodes_from(
    [
        "component",
        "test",
        "state",
        "action"
    ]
)

untyped_base_kami.add_edges_from(
    [
        ("component", "component"),
        ("state", "component"),
        ("component", "action"),
        ("action", "component"),
        ("component", "test"),
        ("action", "state")
    ]
)

kami_basekami = {
    "agent": "component",
    "region": "component",
    "residue": "component",
    "locus": "component",
    "state": "state",
    "mod": "action",
    "syn": "action",
    "deg": "action",
    "bnd": "action",
    "brk": "action",
    "is_bnd": "test",
    "is_free": "test"

}


import pickle

pickle_filepath = './data/raw/pb_cdr3.data'

lines = []
with open('data/raw/pb_cdr3.csv', 'rb') as f:
    lines = f.readlines()
 #store lines to disk
with open(pickle_filepath, 'wb') as pickle_handle:
    pickle.dump(lines, pickle_handle)


from unittest.mock import patch

from sqlmodel import create_engine

from ...conftest import get_testing_print_function

def test_tutorial(clear_sqlmodel):
    from docs_src.tutorial.where import tutorial006 as mod

    mod.sqlite_url = "sqlite://"
    mod.engine = create_engine(mod.sqlite_url)
    calls = []

    new_print = get_testing_print_function(calls)

    with patch("builtins.print", new=new_print):
        mod.main()
    assert calls == [
        [{"name": "Tarantula", "secret_name": "Natalia Roman-on", "age": 32, "id": 4}],
        [{"name": "Black Lion", "secret_name": "Trevor Challa", "age": 35, "id": 5}],
    ]


import setuptools

with open("README.md", "r") as fh:
    long_description = fh.read()

setuptools.setup(
    name="pgevstore",
    version="0.0.1",
    author="Waltton",
    author_email="waltton@2xt.com.br",
    description="Event storage based on PostgreSQL",
    long_description=long_description,
    long_description_content_type="text/markdown",
    url="https://github.com/amopromo/pgevstore",
    packages=['pgevstore', 'pgevstore.client', 'pgevstore.pgpart'],
    classifiers=[],
    entry_points={
        'console_scripts': ['pgpart=pgevstore.pgpart:main'],
    },
    install_requires=[
        'psycopg2-binary',
    ],
)


from datetime import date, timedelta

from menu_planning.actions.lunch_generator import LunchGenerator
from menu_planning.actions.dinner_generator import DinnerGenerator
from menu_planning.actions.starter_generator import StarterGenerator
from menu_planning.services.daily_menu_service import DailyMenuService
from menu_planning.services.lunch_service import LunchService
from menu_planning.services.dinner_service import DinnerService
from menu_planning.services.starter_service import StarterService
from menu_planning.services.menu_service import MenuService

class MenuGenerator:

    MAX_RETRIES = 30

    def __init__(self, starter_service=StarterService(), lunch_service=LunchService(), dinner_service=DinnerService(),
                 daily_menu_service=DailyMenuService(), menu_service=MenuService()):
        self.starter_service = starter_service
        self.lunch_service = lunch_service
        self.dinner_service = dinner_service
        self.daily_menu_service = daily_menu_service
        self.menu_service = menu_service

    def generate(self, days, start_date=date.today(), start_lunch=True, end_dinner=True):

        if days < 1 or (days == 1 and not start_lunch and not end_dinner):
            raise Exception('Dates are not correct')

        # Set variables
        current_lunch = None
        current_dinner = None
        num_lunch_days = 0
        num_dinner_days = 0

        if start_lunch:
            lunch_days_left = days
        else:
            lunch_days_left = days - 1
        if end_dinner:
            dinner_days_left = days
        else:
            dinner_days_left = days - 1
        current_date = start_date

        menu = self.menu_service.create()

        for day in range(days):

            if day == 0 and not start_lunch:
                current_starter = None
                current_lunch = None
                current_dinner = self.dinner_generator(menu.id, lunch_days_left, dinner_days_left)
                num_dinner_days = 1
                dinner_days_left -= 1
            else:
                if lunch_days_left > 0:

                    # Lunch
                    if self.is_lunch_left(current_lunch, num_lunch_days):
                        num_lunch_days += 1
                    elif self.is_mandatory_lunch(current_dinner, menu.id):
                        current_lunch = current_dinner.related_lunch
                        num_lunch_days = 1
                    else:
                        current_lunch = self.lunch_generator(menu.id, lunch_days_left, dinner_days_left,
                                                             is_dinner_left=
                                                             self.is_dinner_left(current_dinner, num_dinner_days))
                        num_lunch_days = 1

                    lunch_days_left -= 1

                    # Starter
                    if current_lunch and current_lunch.need_starter:
                        current_starter = self.starter_generator()
                    else:
                        current_starter = None
                else:
                    current_starter = None
                    current_lunch = None

                if dinner_days_left > 0:

                    # Dinner
                    if self.is_dinner_left(current_dinner, num_dinner_days):
                            num_dinner_days += 1
                    elif self.is_mandatory_dinner(current_lunch, menu.id):
                        current_dinner = current_lunch.related_dinner
                        num_dinner_days = 1
                    else:
                        current_dinner = self.dinner_generator(menu.id, lunch_days_left, dinner_days_left,
                                                               is_lunch_left=
                                                               self.is_lunch_left(current_lunch, num_lunch_days))
                        num_dinner_days = 1

                    dinner_days_left -= 1
                else:
                    current_dinner = None

            dinner_id = current_dinner.id if current_dinner else None
            starter_id = current_starter.id if current_starter else None
            lunch_id = current_lunch.id if current_lunch else None

            self.daily_menu_service.create(current_date, menu.id, lunch_id=lunch_id, dinner_id=dinner_id,
                                           starter_id=starter_id)

            # Update variable
            current_date += timedelta(days=1)

        return menu

    @classmethod
    def lunch_generator(cls, menu_id, lunch_days_left, dinner_days_left, is_dinner_left=False):
        is_found = False
        for i in range(cls.MAX_RETRIES):
            lunch_generator = LunchGenerator(menu_id=menu_id, lunch_days_left=lunch_days_left,
                                             dinner_days_left=dinner_days_left,
                                             is_dinner_left=is_dinner_left)
            if lunch_generator.is_valid():
                lunch = lunch_generator.lunch
                is_found = True
                break
        if not is_found:
            raise Exception('We could not do a menu planning. Try again')
        return lunch

    @classmethod
    def dinner_generator(cls, menu_id, lunch_days_left, dinner_days_left, is_lunch_left=False):
        is_found = False
        for i in range(cls.MAX_RETRIES):
            dinner_generator = DinnerGenerator(menu_id=menu_id, lunch_days_left=lunch_days_left,
                                               dinner_days_left=dinner_days_left,
                                               is_lunch_left=is_lunch_left)
            if dinner_generator.is_valid():
                dinner = dinner_generator.dinner
                is_found = True
                break
        if not is_found:
            raise Exception('We could not do a menu planning. Try again')
        return dinner

    @classmethod
    def starter_generator(cls):
        is_found = False
        for i in range(cls.MAX_RETRIES):
            starter_generator = StarterGenerator()
            if starter_generator.is_valid():
                starter = starter_generator.starter
                is_found = True
                break
        if not is_found:
            raise Exception('We could not do a menu planning. Try again')
        return starter

    @classmethod
    def is_lunch_left(cls, lunch, num_lunch_days):
        return lunch and (lunch.days > num_lunch_days)

    @classmethod
    def is_dinner_left(cls, dinner, num_dinner_days):
        return dinner and (dinner.days > num_dinner_days)

    def is_mandatory_lunch(self, dinner, menu_id):
        return dinner and dinner.related_lunch and not \
            self.lunch_service.get_by_id_and_menu_id(id=dinner.related_lunch.id, menu_id=menu_id)

    def is_mandatory_dinner(self, lunch, menu_id):
        return lunch and lunch.related_dinner_id and not \
            self.dinner_service.get_by_id_and_menu_id(id=lunch.related_dinner_id, menu_id=menu_id)