import argparse
import logging
import os
import sys
import gc
import cv2

import numpy as np
from PIL import Image
import torch
import torch.nn as nn

from utils import ThyroidDataset

from torch import optim
from torch.autograd import Variable
from tqdm import tqdm

from utils.ThyroidDataset import BasicDataset
from utils.eval_demo import eval_demo
from utils.loss import MultipleLoss

from network import densenet

import torchvision.models as models
from tensorboardX import SummaryWriter

from torch.utils.data import DataLoader

from torch.backends import cudnn

from mylogger import Logger

cudnn.benchmark = False
cudnn.deterministic = True
data_dir = "/home/kongming/dagengren/data/Thyroid"
dir_checkpoint = 'checkpoints_src/'
model_dir = "../pretrained_model/model.pth"

class GradCam:
    def __init__(self, model, use_cuda):
        self.model = model
        self.model.eval()
        self.cuda = use_cuda
        if self.cuda:
            self.model = model.cuda()

    def __call__(self, input, classifier_index, index=None):
        # if self.cuda:
        #     features, output = self.extractor(input.cuda(), vector.cuda(), mask_nodule.cuda(), mask_margin.cuda(), aspect.cuda(), classifier_index)#self.forward(input.cuda())
        # else:
        #     features, output = self.extractor(input, vector, mask_nodule, mask_margin, aspect, classifier_index)#self.forward(input)
        #

        input = torch.from_numpy(input.transpose(2, 0, 1)).unsqueeze(0)
        if self.cuda:
            input = input.float().cuda()

        mask_pred, outputs, features = self.model(input)
        output = outputs[classifier_index+1]

        if index == None:
            index = np.argmax(output.cpu().data.numpy())
        pre = np.argmax(output.cpu().data.numpy())

        one_hot = np.zeros((1, output.size()[-1]), dtype=np.float32)
        one_hot[0][index] = 1
        one_hot = Variable(torch.from_numpy(one_hot), requires_grad=True)
        if self.cuda:
            one_hot = torch.sum(one_hot.cuda() * output)
        else:
            one_hot = torch.sum(one_hot * output)

        self.model.zero_grad()
        one_hot.backward(retain_graph=True)
        grads_val = self.model.gradients.cpu().data.numpy()

        target = features
        target = target.cpu().data.numpy()[0, :]

        weights = np.mean(grads_val, axis=(2, 3))[0, :]
        cam = np.zeros(target.shape[1:], dtype=np.float32)

        for i, w in enumerate(weights):
            cam += w * target[i, :, :]

        cam = np.maximum(cam, 0)
        cam = cv2.resize(cam, (224, 224))
        cam = cam - np.min(cam)
        cam = cam / np.max(cam)

        return cam, pre, output, mask_pred

def show_cam_on_image(img, mask, size, rootPath):
    heatmap = cv2.applyColorMap(np.uint8(255*mask), cv2.COLORMAP_JET)
    heatmap = np.float32(heatmap) / 255
    heatmap = cv2.resize(heatmap, (size[0], size[1]))
    cam = heatmap + np.float32(img)/255
    cam = cam / np.max(cam)
    result = np.uint8(255 * cam)
    print(rootPath+"cam.jpg")
    cv2.imwrite(rootPath+"cam.jpg", result)

def test_single(net, device, fname):
    img_pth = "./demo/demo_img/{}".format(fname)
    vis_pth = "./demo/visualization/{}".format(fname.split(".")[0])
    try:
        os.mkdir(vis_pth)
    except OSError:
        pass
    img = Image.open(img_pth)
    orig_size = img.size
    np_img = np.array(img.resize((224,224)))/255
    #print(np_img.shape)
    mask_np = eval_demo(net, np_img, device, log.logger)
    print("Start generate segmentation...")
    mask_img = Image.fromarray(mask_np.astype('uint8')).convert('RGB').resize(orig_size)
    mask_img.save(os.path.join(vis_pth, fname.split(".")[0]+"_seg.jpg"))
    print("Finished.")
    print("Start generate heatmap for attributes...")
    attribute_name = ["Echogenic Foci", "Composition", "Margin", "Echogenicity"]
    target_name = [["none", "exists"], ["solid", "non-solid"],
                   ["ill-defined", "well-defind"], ["hypoechogenic", "hyper-isoechogenic"]]
    typeList = [2, 2, 2, 2]
    grad_cam = GradCam(model=net, use_cuda=True)
    for class_index in range(0, len(typeList)):
        for target_index in range(0, typeList[class_index]):
            cam_mask, output_index, output, mask_pred = grad_cam(np_img, class_index, target_index)
            rootPath = os.path.join(vis_pth, fname.split(".")[0])
            rootPath += "_" + attribute_name[class_index] + '_' + target_name[class_index][target_index] + '_'
            show_cam_on_image(img, cam_mask, orig_size, rootPath)
    print("finished.")

    #log.logger.info('Validation Dice Coeff: {}'.format(val_score))

def get_args():
    parser = argparse.ArgumentParser(description='Train the UNet on images and target masks',
                                     formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument('-f', '--filename', dest='fname', type=str, default="demo1.jpg",
                        help='The file in ./demo/demo_img/')

    return parser.parse_args()

if __name__ == '__main__':

    log = Logger("log_demo.txt")

    args = get_args()
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    seed = 1
    torch.manual_seed(seed)
    if device=="cuda":
        torch.cuda.manual_seed(seed)

    log.logger.info(f'Using device {device}')

    log.logger.info(f'Start initialize model...')

    net_pretrained=models.densenet201(pretrained=True)
    net = densenet.densenet201(num_classes=1)

    net_dict = net.state_dict()

    for k in net_pretrained.features.state_dict():
        k_S = k.split(".")
        block_num=int(k_S[0][-1])
        if block_num==5:
            k_net = "block_4."
            for i in k_S:
                k_net=k_net+i+"."
            k_net = k_net[:-1]
        else:
            #k_S[0]="block_{}".format(block_num)
            k_net = "block_{}.".format(block_num)
            for i in k_S:
                k_net=k_net+i+"."
            k_net = k_net[:-1]

        #print(k)
        #print(k_net)

        v = net_pretrained.features.state_dict()[k]
        net_dict[k_net]=v

    net.load_state_dict(net_dict)
    log.logger.info(f'Model loaded from {model_dir}')
    net.load_state_dict(
        torch.load(model_dir, map_location=device)
    )

    log.logger.info(f'Model initialized')
    net.to(device=device)

    print("Filename: {}".format(args.fname))
    try:
        test_single(net, device, args.fname)
    except KeyboardInterrupt:
        try:
            sys.exit(0)
        except SystemExit:
            os._exit(0)


from pymongo import MongoClient           # pymongo를 임포트 하기(패키지 인스톨 먼저 해야겠죠?)
client = MongoClient('localhost', 27017)  # mongoDB는 27017 포트로 돌아갑니다.
db = client.pl
db2=client.K
pig_seoul = list(db.bob.find({'media': '맛있는 녀석들', 'area': '서울'}, {'_id': 0}))
print(pig_seoul)

num2 =list(db.K.find({},{'_id':0,'dish':0,'add':0,'media':0,'area':0}))

# 검색할 주소


rint('Welcome to Atom Counter!')
print('Substance List: Gold, Carbon, Hydrogen')
a=input('Please Pick Which Substance You Would Like To Count: ')
b=float(input('Please Input The Amount of '+a+' in grams: '))
if a=='Gold':
    def num_atoms(b,w):
        m=b/w
        g=m*6.022*10**23
        print('The Amount of Atoms in Your '+a+' is:',g)
    num_atoms(b,196.97)
elif a=='Carbon':
    def num_atoms(b,w):
        m=b/w
        c=m*6.022*10**23
        print('The Amount of Atoms in Your '+a+' is:',c)
    num_atoms(b,12.001)
else:
    def num_atoms(b,w):
        m=b/w
        h=m*6.022*10**23
        print('The Amount of Atoms in Your '+a+' is:',h)
    num_atoms(b,1.008)

1st Semester/Sir Jude's Exercise 3/Exercise 3.py


# -*- coding: utf-8 -*-
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from qgis.core import *
from qgis.gui import *

from Ui_options import Ui_Options

class OptionsDialog(QDialog, Ui_Options):

    def __init__(self, parent):
        QDialog.__init__(self, parent)
        self.setupUi(self)

        self.bar = QgsMessageBar(self)
        self.bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding)
        self.gridLayout.addWidget(self.bar, 0, 0, 1, 1, Qt.AlignTop)

        self.okButton = self.buttonBox.button(QDialogButtonBox.Ok)
        self.connect(self.okButton, SIGNAL("accepted()"), self.accept)

        self.settings = QSettings("CatAIS","Qcadastre")
        self.projectsdatabase = self.settings.value("options/general/projectsdatabase")
        self.projectsdatabasepath = QFileInfo(self.projectsdatabase).absolutePath()
        self.projectsrootdir = self.settings.value("options/general/projectsrootdir")
        self.importjar = self.settings.value("options/import/jar")
        self.importjarpath = QFileInfo(self.importjar).absolutePath()
        self.providername = self.settings.value("options/db/provider")

    def initGui(self):
        self.lineEditDbPort.setValidator(QRegExpValidator(QRegExp("[0-9]+"), self.lineEditDbPort))

        self.lineEditProjectsDatabase.setText(self.settings.value("options/general/projectsdatabase"))
        self.lineEditProjectsRootDir.setText(self.settings.value("options/general/projectsrootdir"))

        self.lineEditImportJar.setText(self.settings.value("options/import/jar"))
        vmarguments = self.settings.value("options/import/vmarguments")
        if vmarguments == "":
            self.plainTextEditImportVMArguments.insertPlainText("-Xms128m -Xmx1024m")
        else:
            self.plainTextEditImportVMArguments.insertPlainText(vmarguments)

        self.cmbBoxDbProvider.addItem ("PostgreSQL", "postgres")
        self.cmbBoxDbProvider.addItem ("SQLite", "sqlite")
        idx = self.cmbBoxDbProvider.findData(self.providername)
        self.cmbBoxDbProvider.setCurrentIndex(idx)

        self.lineEditDbHost.setText(self.settings.value("options/db/host"))
        self.lineEditDbDatabase.setText(self.settings.value("options/db/name"))
        self.lineEditDbPort.setText(self.settings.value("options/db/port"))
        self.lineEditDbUser.setText(self.settings.value("options/db/user"))
        self.lineEditDbUserPwd.setText(self.settings.value("options/db/pwd"))
        self.lineEditDbAdmin.setText(self.settings.value("options/db/admin"))
        self.lineEditDbAdminPwd.setText(self.settings.value("options/db/adminpwd"))

        QWidget.setTabOrder(self.cmbBoxDbProvider, self.lineEditDbHost)
        QWidget.setTabOrder(self.lineEditDbHost, self.lineEditDbDatabase)
        QWidget.setTabOrder(self.lineEditDbDatabase, self.lineEditDbPort)
        QWidget.setTabOrder(self.lineEditDbPort, self.lineEditDbUser)
        QWidget.setTabOrder(self.lineEditDbUser, self.lineEditDbUserPwd)
        QWidget.setTabOrder(self.lineEditDbUserPwd, self.lineEditDbAdmin)
        QWidget.setTabOrder(self.lineEditDbAdmin, self.lineEditDbAdminPwd)

        self.listWidgetModelRepos.clear()
        size = self.settings.beginReadArray("options/modelrepositories")
        for i in range(size):
            self.settings.setArrayIndex(i)
            item = QListWidgetItem(self.settings.value("url"))
            item.setFlags(item.flags() | Qt.ItemIsEditable)
            self.listWidgetModelRepos.addItem(item)
        self.settings.endArray();

    @pyqtSignature("on_btnAddModelRepo_clicked()")
    def on_btnAddModelRepo_clicked(self):
        item = QListWidgetItem("http://")
        item.setFlags(item.flags() | Qt.ItemIsEditable)
        self.listWidgetModelRepos.addItem(item)
        self.listWidgetModelRepos.setFocus()
        self.listWidgetModelRepos.setCurrentRow(self.listWidgetModelRepos.count() - 1)

    @pyqtSignature("on_btnDeleteModelRepo_clicked()")
    def on_btnDeleteModelRepo_clicked(self):
        selectedItems = self.listWidgetModelRepos.selectedItems()
        for selectedItem in self.listWidgetModelRepos.selectedItems():
            self.listWidgetModelRepos.takeItem(self.listWidgetModelRepos.row(selectedItem))

    @pyqtSignature("on_btnBrowseImportJar_clicked()")
    def on_btnBrowseImportJar_clicked(self):
        file = QFileDialog.getOpenFileName(self, "Open import jar file", self.importjarpath, "jar (*.jar *.JAR)")
        fileInfo = QFileInfo(file)
        self.lineEditImportJar.setText(fileInfo.absoluteFilePath())

    @pyqtSignature("on_btnBrowseProjectsDatabase_clicked()")
    def on_btnBrowseProjectsDatabase_clicked(self):
        file = QFileDialog.getOpenFileName(self, QCoreApplication.translate("Qcadastre", "Choose projects definitions database"), self.projectsdatabasepath,  "SQLite (*.sqlite *.db *.DB)")
        fileInfo = QFileInfo(file)
        self.lineEditProjectsDatabase.setText(fileInfo.absoluteFilePath())

    @pyqtSignature("on_btnBrowseProjectsRootDir_clicked()")
    def on_btnBrowseProjectsRootDir_clicked(self):
        dir = QFileDialog.getExistingDirectory(self, QCoreApplication.translate("Qcadastre", "Choose projects root directory"), self.projectsrootdir)
        dirInfo = QFileInfo(dir)
        self.lineEditProjectsRootDir.setText(dirInfo.absoluteFilePath())

    @pyqtSignature("on_cmbBoxDbProvider_currentIndexChanged(int)")
    def on_cmbBoxDbProvider_currentIndexChanged(self, idx):
        self.provider = self.cmbBoxDbProvider.itemData (idx)

        if self.provider == "postgres":
            self.lblDbHost.setEnabled(True)
            self.lineEditDbHost.setEnabled(True)
            self.lblDbDatabase.setEnabled(True)
            self.lineEditDbDatabase.setEnabled(True)
            self.lblDbPort.setEnabled(True)
            self.lineEditDbPort.setEnabled(True)
            self.lblDbUser.setEnabled(True)
            self.lineEditDbUser.setEnabled(True)
            self.lblDbUserPwd.setEnabled(True)
            self.lineEditDbUserPwd.setEnabled(True)
            self.lblDbAdmin.setEnabled(True)
            self.lineEditDbAdmin.setEnabled(True)
            self.lblDbAdminPwd.setEnabled(True)
            self.lineEditDbAdminPwd.setEnabled(True)

        elif self.provider == "sqlite":
            self.lblDbHost.setEnabled(False)
            self.lineEditDbHost.setEnabled(False)
            self.lblDbDatabase.setEnabled(False)
            self.lineEditDbDatabase.setEnabled(False)
            self.lblDbPort.setEnabled(False)
            self.lineEditDbPort.setEnabled(False)
            self.lblDbUser.setEnabled(False)
            self.lineEditDbUser.setEnabled(False)
            self.lblDbUserPwd.setEnabled(False)
            self.lineEditDbUserPwd.setEnabled(False)
            self.lblDbAdmin.setEnabled(False)
            self.lineEditDbAdmin.setEnabled(False)
            self.lblDbAdminPwd.setEnabled(False)
            self.lineEditDbAdminPwd.setEnabled(False)

            self.bar.pushMessage("Warning", "SQLite provider is not yet implemented.", level=QgsMessageBar.WARNING, duration=5)

    def accept(self):
        self.settings.setValue("options/general/projectsdatabase", self.lineEditProjectsDatabase.text())
        self.emit(SIGNAL("projectsFileHasChanged()"))

        self.settings.setValue("options/general/projectsrootdir", self.lineEditProjectsRootDir.text())

        self.settings.setValue("options/import/jar", self.lineEditImportJar.text())
        self.settings.setValue("options/import/vmarguments", self.plainTextEditImportVMArguments.toPlainText())

        self.settings.setValue("options/db/provider", self.provider)
        self.settings.setValue("options/db/host", self.lineEditDbHost.text())
        self.settings.setValue("options/db/name", self.lineEditDbDatabase.text())
        self.settings.setValue("options/db/port", self.lineEditDbPort.text())
        self.settings.setValue("options/db/user", self.lineEditDbUser.text())
        self.settings.setValue("options/db/pwd", self.lineEditDbUserPwd.text())
        self.settings.setValue("options/db/admin", self.lineEditDbAdmin.text())
        self.settings.setValue("options/db/adminpwd", self.lineEditDbAdminPwd.text())

        self.settings.beginWriteArray("options/modelrepositories");
        for i in range(self.listWidgetModelRepos.count()):
            self.settings.setArrayIndex(i);
            self.settings.setValue("url", self.listWidgetModelRepos.item(i).text());
        self.settings.endArray();

        self.close()


"""
임의의 실수 a,b,c가 주어졌을 때, 2차방정식 ax^2+bx+c=0의 근을 구하는 알고리즘을 작성한 뒤,
코드로 구현하라.
- 단, 제곱근의 계산을 위해서 math 모듈의 sqrt()란 함수를 사용할 수 잇다.

1. det <- b*b-4ac를 계산
2. det 음수이면 허근이 나오므로 오류를 반환
3. det 0이면 중근
4. 두 개의 근(-b(+-)sqrt(det))/2a
"""

import math

a = float(input("Enter a :"))
b = float(input("Enter b :"))
c = float(input("Enter c :"))

det = b*b-4*a*c

if det < 0:
    print("판별식이 0보다 작습니다.")
elif det == 0:
    print("중근 : ", -1*b/(2*a))
else:
    print("X1 : ", (-1*b+math.sqrt(det))/(2*a))
    print("X2 : ", (-1*b-math.sqrt(det))/(2*a))


"""
Definitions of basic 'edits' (Actions) to transform a product into synthons and reactants
"""
from abc import ABCMeta, abstractmethod
from typing import Optional, Tuple

from rdkit import Chem
from rdkit.Chem import Mol, rdchem

from utils.chem import attach_lg, fix_Hs_Charge, get_atom_Chiral, get_bond_stereo

MAX_BONDS = {'C': 4, 'N': 3, 'O': 2, 'Br': 1, 'Cl': 1, 'F': 1, 'I': 1}

class ReactionAction(metaclass=ABCMeta):
    def __init__(self, atom_map1: int, atom_map2: int, action_vocab: str):
        self.atom_map1 = atom_map1
        self.atom_map2 = atom_map2
        self.action_vocab = action_vocab

    @abstractmethod
    def get_tuple(self) -> Tuple[str, ...]:
        raise NotImplementedError('Abstract method')

    @abstractmethod
    def apply(self, mol: Mol) -> Mol:
        raise NotImplementedError('Abstract method')

class AtomEditAction(ReactionAction):
    def __init__(self, atom_map1: int, num_explicit_hs: int, chiral_tag: int, action_vocab: str):
        super(AtomEditAction, self).__init__(atom_map1, -1, action_vocab)
        self.num_explicit_hs = num_explicit_hs
        self.chiral_tag = chiral_tag

    @property
    def feat_vals(self) -> Tuple[int, int]:
        return self.num_explicit_hs, self.chiral_tag

    def get_tuple(self) -> Tuple[str, Tuple[int, int]]:
        return self.action_vocab, self.feat_vals

    def apply(self, mol: Mol) -> Mol:
        new_mol = Chem.RWMol(mol)
        amap_idx = {atom.GetAtomMapNum(): atom.GetIdx() for atom in new_mol.GetAtoms()
                    if atom.GetAtomMapNum() != 0}
        atom_idx = amap_idx[self.atom_map1]
        atom = new_mol.GetAtomWithIdx(atom_idx)
        atom.SetNumExplicitHs(self.num_explicit_hs)
        a_chiral = rdchem.ChiralType.values[self.chiral_tag]
        atom.SetChiralTag(a_chiral)
        pred_mol = new_mol.GetMol()
        return pred_mol

    def __str__(self):
        return f'Edit Atom {self.atom_map1}: Num explicit Hs={self.num_explicit_hs}, Chiral_tag={self.chiral_tag}'

class BondEditAction(ReactionAction):
    def __init__(self, atom_map1: int, atom_map2: int,
                 bond_type: Optional[int], bond_stereo: Optional[int],
                 action_vocab: str):
        super(BondEditAction, self).__init__(
            atom_map1, atom_map2, action_vocab)
        self.bond_type = bond_type
        self.bond_stereo = bond_stereo

    def get_tuple(self) -> Tuple[str, Tuple[Optional[int], Optional[int]]]:
        return self.action_vocab, (self.bond_type, self.bond_stereo)

    def apply(self, mol: Mol) -> Mol:
        new_mol = Chem.RWMol(mol)
        amap_idx = {atom.GetAtomMapNum(): atom.GetIdx() for atom in new_mol.GetAtoms()
                    if atom.GetAtomMapNum() != 0}
        atom1 = new_mol.GetAtomWithIdx(amap_idx[self.atom_map1])
        atom2 = new_mol.GetAtomWithIdx(amap_idx[self.atom_map2])

        if self.bond_type is None:  # delete bond
            bond = new_mol.GetBondBetweenAtoms(atom1.GetIdx(), atom2.GetIdx())
            new_mol.RemoveBond(atom1.GetIdx(), atom2.GetIdx())
            pred_mol = new_mol.GetMol()

        else:
            b_type = rdchem.BondType.values[self.bond_type]
            b_stereo = rdchem.BondStereo.values[self.bond_stereo]

            bond = new_mol.GetBondBetweenAtoms(atom1.GetIdx(), atom2.GetIdx())
            b1 = bond.GetBondTypeAsDouble()
            if bond is None:  # add new bond
                pass
            else:  # change an existing bond
                bond.SetBondType(b_type)
                bond.SetStereo(b_stereo)
                b2 = bond.GetBondTypeAsDouble()

                val = b1 - b2
                if val > 0:
                    atom1.SetNumExplicitHs(int(atom1.GetNumExplicitHs() + val))
                    atom2.SetNumExplicitHs(int(atom2.GetNumExplicitHs() + val))

                elif val < 0:
                    atom1.SetNumExplicitHs(
                        int(max(0, atom1.GetNumExplicitHs() + val)))
                    atom2.SetNumExplicitHs(
                        int(max(0, atom2.GetNumExplicitHs() + val)))

                if atom1.GetSymbol() == 'S' and atom2.GetSymbol() == 'O':
                    if b1 == 1.0 and b2 == 2.0 and atom2.GetFormalCharge() == -1:
                        atom2.SetFormalCharge(0)
                    if b1 == 2.0 and b2 == 1.0 and atom2.GetFormalCharge() == 0:
                        atom1.SetNumExplicitHs(0)

                elif atom2.GetSymbol() == 'S' and atom1.GetSymbol() == 'O':
                    if b1 == 1.0 and b2 == 2.0 and atom1.GetFormalCharge() == -1:
                        atom1.SetFormalCharge(0)
                    if b1 == 2.0 and b2 == 1.0 and atom1.GetFormalCharge() == 0:
                        atom2.SetNumExplicitHs(0)

                elif atom1.GetSymbol() == 'C' and atom2.GetSymbol() == 'N':
                    if b1 == 3.0 and b2 == 1.0 and atom1.GetFormalCharge() == -1 and atom2.GetFormalCharge() == 1:
                        atom1.SetFormalCharge(0)
                        atom2.SetFormalCharge(0)
                        atom1.SetNumExplicitHs(1)
                        atom2.SetNumExplicitHs(1)

                elif atom2.GetSymbol() == 'C' and atom1.GetSymbol() == 'N':
                    if b1 == 3.0 and b2 == 1.0 and atom2.GetFormalCharge() == -1 and atom1.GetFormalCharge() == 1:
                        atom1.SetFormalCharge(0)
                        atom2.SetFormalCharge(0)
                        atom1.SetNumExplicitHs(1)
                        atom2.SetNumExplicitHs(1)

            pred_mol = new_mol.GetMol()
            # fix explicit Hs and charge
            pred_mol = fix_Hs_Charge(pred_mol)

        return pred_mol

    def __str__(self):
        if self.bond_type is None:
            return f'Delete bond {self.atom_map1, self.atom_map2}'
        bond_feat = f'Bond type={self.bond_type}, Bond Stereo={self.bond_stereo}'
        return f'{self.action_vocab} {self.atom_map1, self.atom_map2}: {bond_feat}'

class AddGroupAction(ReactionAction):
    def __init__(self, atom_map1: int, leaving_group: str, action_vocab: str):
        super(AddGroupAction, self).__init__(atom_map1, -1, action_vocab)
        self.leaving_group = leaving_group

    def get_tuple(self) -> Tuple[str, str]:
        return self.action_vocab, self.leaving_group

    def apply(self, mol: Mol) -> Mol:
        lg_mol = Chem.MolFromSmiles(self.leaving_group)
        Chem.Kekulize(lg_mol)
        try:
            pred_mol = attach_lg(main_mol=mol, lg_mol=lg_mol,
                                 attach_atom_map=self.atom_map1)
        except Exception as e:
            print('fail to attach lg')
            pred_mol = mol
        # fix explicit Hs and charge
        pred_mol = fix_Hs_Charge(pred_mol)
        return pred_mol

    def __str__(self):
        return f'Attaching {self.leaving_group} to atom {self.atom_map1}'

class Termination(ReactionAction):
    def __init__(self, action_vocab: str):
        super(Termination, self).__init__(-1, -1, action_vocab=action_vocab)

    def get_tuple(self) -> Tuple[str]:
        return self.action_vocab

    def apply(self, mol: Mol) -> Mol:

        atom_chiral = get_atom_Chiral(mol)
        bond_stereo = get_bond_stereo(mol)
        if all(int(bt) == 0 for bt in bond_stereo.values()) and all(int(chiral) == 0 for chiral in atom_chiral.values()):
            mol = Chem.MolFromSmiles(
                Chem.MolToSmiles(mol, isomericSmiles=False))
            return mol

        mol = Chem.MolFromSmiles(Chem.MolToSmiles(mol))

        # Dealing with the inconsistency between molecular mol chirality and atomic chiral tag
        for atom in mol.GetAtoms():
            amap_num = atom.GetAtomMapNum()
            atom.SetChiralTag(atom_chiral[amap_num])

        # Dealing with the inconsistency between molecular mol stereo and bond stereo
        amap_idx = {atom.GetAtomMapNum(): atom.GetIdx()
                    for atom in mol.GetAtoms()}
        for bond in mol.GetBonds():
            a1, a2 = bond.GetBeginAtom().GetAtomMapNum(), bond.GetEndAtom().GetAtomMapNum()
            atom1 = mol.GetAtomWithIdx(amap_idx[a1])
            atom2 = mol.GetAtomWithIdx(amap_idx[a2])
            bond_atoms = sorted([a1, a2])
            st = bond_stereo[tuple(bond_atoms)]

            a1_max_neigh = None
            a2_max_neigh = None
            bond.SetStereo(st)

            if int(st) != 0 and len(list(bond.GetStereoAtoms())) < 2:
                if len([a.GetAtomicNum() for a in atom1.GetNeighbors() if a.GetIdx() != atom2.GetIdx()]) == 0 or len(
                        [a.GetAtomicNum() for a in atom2.GetNeighbors() if a.GetIdx() != atom1.GetIdx()]) == 0:
                    continue
                else:
                    a1_max_neigh_num = max(
                        [a.GetAtomicNum() for a in atom1.GetNeighbors() if a.GetIdx() != atom2.GetIdx()])
                    a2_max_neigh_num = max(
                        [a.GetAtomicNum() for a in atom2.GetNeighbors() if a.GetIdx() != atom1.GetIdx()])

                    for a in atom1.GetNeighbors():
                        if a.GetAtomicNum() == a1_max_neigh_num and a.GetIdx() != atom2.GetIdx():
                            a1_max_neigh = a.GetIdx()
                    for a in atom2.GetNeighbors():
                        if a.GetAtomicNum() == a2_max_neigh_num and a.GetIdx() != atom1.GetIdx():
                            a2_max_neigh = a.GetIdx()

                    if all(a.GetAtomicNum() == a1_max_neigh_num for a in atom1.GetNeighbors() if
                           a.GetIdx() != atom2.GetIdx()) and len(
                            [a.GetAtomicNum() for a in atom1.GetNeighbors() if a.GetIdx() != atom2.GetIdx()]) == 2:
                        a11_max_neigh_num = 0
                        for a in atom1.GetNeighbors():
                            if a.GetIdx() != atom2.GetIdx():
                                if len([a1.GetAtomicNum() for a1 in a.GetNeighbors() if
                                        a1.GetIdx() != atom1.GetIdx()]) == 0:
                                    continue
                                else:
                                    a11_max_neigh_num = max(a11_max_neigh_num,
                                                            max([a1.GetAtomicNum() for a1 in a.GetNeighbors() if
                                                                 a1.GetIdx() != atom1.GetIdx()]))
                        for a in atom1.GetNeighbors():
                            if a.GetIdx() != atom2.GetIdx():
                                for a1 in a.GetNeighbors():
                                    if a1.GetAtomicNum() == a11_max_neigh_num and a1.GetIdx() != atom1.GetIdx():
                                        a1_max_neigh = a.GetIdx()

                    if all(a.GetAtomicNum() == a2_max_neigh_num for a in atom2.GetNeighbors() if
                           a.GetIdx() != atom1.GetIdx()) and len(
                            [a.GetAtomicNum() for a in atom2.GetNeighbors() if a.GetIdx() != atom1.GetIdx()]) == 2:
                        a12_max_neigh_num = 0
                        for a in atom2.GetNeighbors():
                            if a.GetIdx() != atom1.GetIdx():
                                if len([a2.GetAtomicNum() for a2 in a.GetNeighbors() if
                                        a2.GetIdx() != atom2.GetIdx()]) == 0:
                                    continue
                                else:
                                    a12_max_neigh_num = max(a12_max_neigh_num,
                                                            max([a2.GetAtomicNum() for a2 in a.GetNeighbors() if
                                                                 a2.GetIdx() != atom2.GetIdx()]))
                        for a in atom2.GetNeighbors():
                            if a.GetIdx() != atom1.GetIdx():
                                for a2 in a.GetNeighbors():
                                    if a2.GetAtomicNum() == a12_max_neigh_num and a2.GetIdx() != atom2.GetIdx():
                                        a2_max_neigh = a.GetIdx()

                if a1_max_neigh is not None and a2_max_neigh is not None:
                    try:
                        bond.SetStereoAtoms(a1_max_neigh, a2_max_neigh)
                    except:
                        bond.SetStereoAtoms(a2_max_neigh, a1_max_neigh)

        return mol

    def __str__(self):
        return 'Terminate'


# -*- coding: utf-8 -*-
import os
import string
from typing import Union

import hydra
import pandas as pd
import torch
from omegaconf import DictConfig
from torchvision import transforms

from src import _PROJECT_ROOT
from src.data.Lego_Dataset import Lego_Dataset

# processes the data from the external data and creates a Dataset of type Lego Dataset,
# adds various transformations for data augmentation
# samples indices from the train data_set to create a split between training and validation set
# and saves both files under processed

def make_dataset(config: DictConfig, dataset_type: string) -> Lego_Dataset:
    index_file = get_index_file(dataset_type)
    data_labels = index_file["class_id"] - 1
    data_files = index_file["path"]
    dataset = Lego_Dataset(
        file_paths=data_files,
        labels=data_labels,
        transform=get_transform(config, dataset_type=dataset_type),
    )
    # save_dataset(dataset= dataset, dataset_type = dataset_type)
    return dataset

# implements the split between training and validation data
# ToDo add Typing return value
def train_val_split(
    input_path_index: pd.DataFrame,
) -> Union[pd.DataFrame, pd.DataFrame]:
    # takes the path and label information from the provided index.csv file
    index = pd.read_csv(input_path_index)
    # Train Validation Split taking 3/4 of the training set as validation
    train_index = index.sample(int(0.75 * len(index.index)), random_state=42)

    remaining_indices = list(set(index.index) - set(train_index.index))
    # Create a new DataFrame with the remaining indices
    train_index.reset_index(inplace=True, drop=True)
    val_index = index.loc[remaining_indices].reset_index(drop=True)

    return train_index, val_index

# converts the label to the actual name of the minifigure
def convert_label(label: int) -> string:
    meta_data = pd.read_csv(
        os.path.join(_PROJECT_ROOT, "data", "external", "lego_dataset", "metadata.csv")
    )

    return (
        meta_data["minifigure_name"].loc[meta_data["class_id"] == label + 1].to_string()
    )

def get_transform(config: DictConfig, dataset_type: string):
    augmentation = []

    dataset_type = dataset_type + "_transforms"
    # Check if the dataset type is present in the configuration
    if dataset_type in config.augmentation.keys():
        # Access the transformations for the current dataset type
        dataset_transforms = config.augmentation[dataset_type]

        # Iterate over the transformations defined in the configuration
        for _, conf in dataset_transforms.items():
            if "_target_" in conf:
                # Instantiate and append the augmentation transform
                augmentation.append(hydra.utils.instantiate(conf))
    else:
        raise ValueError("Invalid dataset_type. It must be 'train', 'val', or 'test'.")

    # Create a composition of all augmentation transforms for the current dataset type
    return transforms.Compose(augmentation)

def get_index_file(dataset_type) -> pd.DataFrame:
    if dataset_type == "train":
        index, _ = train_val_split(
            os.path.join(_PROJECT_ROOT, "data", "external", "lego_dataset", "index.csv")
        )
    elif dataset_type == "val":
        _, index = train_val_split(
            os.path.join(_PROJECT_ROOT, "data", "external", "lego_dataset", "index.csv")
        )
    elif dataset_type == "test":
        index = pd.read_csv(
            os.path.join(_PROJECT_ROOT, "data", "external", "lego_dataset", "test.csv")
        )
    else:
        raise ValueError("Invalid dataset_type. It must be 'train', 'val', or 'test'.")
    return index

# saves processed data as tensors in a file
def save_dataset(dataset: Lego_Dataset, dataset_type: string) -> None:
    # Create an empty dictionary to store your data points.
    data_dict = {"images": [], "labels": []}

    # Loop through the dataset and collect image tensors and labels.
    for idx in range(len(dataset)):
        image_tensor, label = dataset[idx]  # Get image tensor and label.

        # Append the image tensor and label to the dictionary.
        data_dict["images"].append(image_tensor)
        data_dict["labels"].append(label)

        # Specify the directory and filename where you want to save the dictionary.
        save_path = os.path.join(
            _PROJECT_ROOT, "data", "processed", dataset_type + "_dataset.pth"
        )

        # Save the dictionary containing all data points to a single file.
        torch.save(data_dict, save_path)


import matplotlib.pyplot as plt
import sys
import numpy as np
from scipy import signal

filename = sys.argv[1]
print("File to show " + filename)

with open(filename,"rb") as f:
    raw = f.read()

time = list(range(len(raw)))
data = list(range(len(raw)))

for x in range(len(raw)):
    data[x] = raw[x]

print("Samples read " + str(len(data)))

arr_data = np.array(data, dtype=int)

plt.plot(time, arr_data)
plt.xlabel('Samples')
plt.ylabel('Position (??)')
plt.show()

# def compare_signal(raw_signal, **kwargs):
    # fig, ax1 = plt.subplots(1, 1)
    # ax1.plot(raw_signal, 'k-', label='Raw Signal')
    # for i, (sig_name, sig_value) in enumerate(kwargs.items()):
        # ax1.plot(sig_value, '-.', lw=4, alpha=0.5, label=sig_name)
    # ax1.legend()
    # plt.show()

# simple_1d = np.arange(0, 1024, 4)
# simple_1d_wrapped = simple_1d % 255
# compare_signal(simple_1d, Wrapped=simple_1d_wrapped)
# compare_signal(simple_1d, Wrapped=simple_1d_wrapped, Unwrapped=np.unwrap(simple_1d_wrapped, discont=127))


import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtPrintSupport import QPrintDialog, QPrinter
from ui.ui import Ui_Form
from ui.mouse_event import GraphicsScene
import cv2
import numpy as np
from options.test_options import TestOptions
# from model import Model
import os
import time
import torch
import pix2pixHD_model as pix
from models import create_model
COUNT=0
class Ex(QWidget, Ui_Form):
    def __init__(self, model):
        super().__init__()

        self.setupUi(self)
        self.show()
        self.model = model

        self.output_img = None

        self.mat_img = None

        self.ld_mask = None
        self.ld_sk = None

        self.modes = [0,0,0]
        self.mouse_clicked = False
        self.scene = GraphicsScene(self.modes)
        self.graphicsView.setScene(self.scene)
        self.graphicsView.setAlignment(Qt.AlignTop | Qt.AlignLeft)
        self.graphicsView.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.graphicsView.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

        self.result_scene = QGraphicsScene()
        self.graphicsView_2.setScene(self.result_scene)
        self.graphicsView_2.setAlignment(Qt.AlignTop | Qt.AlignLeft)
        self.graphicsView_2.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.graphicsView_2.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

        self.dlg = QColorDialog(self.graphicsView)
        self.color = None

    def mode_select(self, mode):
        for i in range(len(self.modes)):
            self.modes[i] = 0
        self.modes[mode] = 1

    def open(self):
        fileName, _ = QFileDialog.getOpenFileName(self, "Open File",
                QDir.currentPath())
        if fileName:
            image = QPixmap(fileName)
            # print(fileName)
            file_label='test_label/'+fileName.split('/')[-1]
            # print(file_label)
            file_label=file_label.split('.')[0]+'.png'
            self.mat_label=cv2.imread(file_label)
            # print(self.mat_label)

            self.mat_label=cv2.cvtColor(self.mat_label,cv2.COLOR_BGR2GRAY)
            mat_img = cv2.imread(fileName)

            mat_img=cv2.cvtColor(mat_img,cv2.COLOR_BGR2RGB)
            if image.isNull():
                QMessageBox.information(self, "Image Viewer",
                        "Cannot load %s." % fileName)
                return

            # redbrush = QBrush(Qt.red)
            # blackpen = QPen(Qt.black)
            # blackpen.setWidth(5)
            self.image = image.scaled(self.graphicsView.size(), Qt.IgnoreAspectRatio)
            mat_img = mat_img/127.5 - 1
            self.mat_img = np.expand_dims(mat_img,axis=0)
            # cv2.imshow('a',self.mat_img.squeeze())
            # cv2.waitKey(0)
            self.scene.reset()
            if len(self.scene.items())>0:
                self.scene.reset_items()
            self.scene.addPixmap(self.image)
            if len(self.result_scene.items())>0:
                self.result_scene.removeItem(self.result_scene.items()[-1])
            self.result_scene.addPixmap(self.image)

    def mask_mode(self):
        self.mode_select(0)

    def sketch_mode(self):
        self.mode_select(1)

    def stroke_mode(self):
        if not self.color:
            self.color_change_mode()
        self.scene.get_stk_color(self.color)
        self.mode_select(2)

    def color_change_mode(self):
        self.dlg.exec_()
        self.color = self.dlg.currentColor().name()
        self.pushButton_4.setStyleSheet("background-color: %s;" % self.color)
        self.scene.get_stk_color(self.color)

    def complete(self):
        global COUNT
        sketch = self.make_sketch(self.scene.sketch_points)
        sketch=torch.FloatTensor(sketch)
        stroke = self.make_stroke(self.scene.stroke_points)
        stroke=torch.FloatTensor(stroke)
        mask = self.make_mask(self.scene.mask_points)
        mask=torch.FloatTensor(mask)
        self.mat_img=torch.FloatTensor(self.mat_img)
        if not type(self.ld_mask)==type(None):
            ld_mask = np.expand_dims(self.ld_mask[:,:,0:1],axis=0)
            ld_mask[ld_mask>0] = 1
            ld_mask[ld_mask<1] = 0
            mask = mask+ld_mask
            mask[mask>0] = 1
            mask[mask<1] = 0
            mask = np.asarray(mask,dtype=np.uint8)

        if not type(self.ld_sk)==type(None):
            sketch = sketch+self.ld_sk
            sketch[sketch>0]=1

        noise = self.make_noise()
        noise=torch.FloatTensor(noise)
        self.mat_label=torch.FloatTensor(self.mat_label)
        self.mat_label=self.mat_label.reshape(1,512,320,1)

        start_t = time.time()
        result = self.model.inference(self.mat_label.permute(0,3,1,2).cuda()
                                      ,self.mat_img.permute(0,3,1,2).cuda()
                                      ,sketch.permute(0,3,1,2).cuda()
                                      ,stroke.permute(0,3,1,2).cuda()
                                      ,mask.permute(0,3,1,2).cuda()
                                      ,noise.permute(0,3,1,2).cuda())
        result=result*mask.permute(0,3,1,2).cuda()+self.mat_img.permute(0,3,1,2).cuda()*(1-mask.permute(0,3,1,2).cuda())
        end_t = time.time()
        print('inference time : {}'.format(end_t-start_t))
        result = (result+1)*127.5
        result=result.permute(0,2,3,1)
        result=result.cpu().numpy()
        result = np.asarray(result[0,:,:,:],dtype=np.uint8)

        self.output_img = result
        qim = QImage(result.data, result.shape[1], result.shape[0], result.strides[0], QImage.Format_RGB888)
        self.result_scene.removeItem(self.result_scene.items()[-1])
        self.result_scene.addPixmap(QPixmap.fromImage(qim))
        COUNT+=1

    def make_noise(self):
        noise = np.zeros([512, 320, 1],dtype=np.uint8)
        noise = cv2.randn(noise, 0, 255)
        noise = np.asarray(noise/255,dtype=np.uint8)
        noise = np.expand_dims(noise,axis=0)
        return noise

    def make_mask(self, pts):
        if len(pts)>0:
            mask = np.zeros((512,320,3))
            for pt in pts:
                cv2.line(mask,pt['prev'],pt['curr'],(255,255,255),12)
            mask = np.asarray(mask[:,:,0]/255,dtype=np.uint8)
            mask = np.expand_dims(mask,axis=2)
            mask = np.expand_dims(mask,axis=0)
        else:
            mask = np.zeros((512,320,3))
            mask = np.asarray(mask[:,:,0]/255,dtype=np.uint8)
            mask = np.expand_dims(mask,axis=2)
            mask = np.expand_dims(mask,axis=0)
        return mask

    def make_sketch(self, pts):
        if len(pts)>0:
            sketch = np.zeros((512,320,3))
            # sketch = 255*sketch
            for pt in pts:
                cv2.line(sketch,pt['prev'],pt['curr'],(255,255,255),1)
            sketch = np.asarray(sketch[:,:,0]/255,dtype=np.uint8)
            sketch = np.expand_dims(sketch,axis=2)
            sketch = np.expand_dims(sketch,axis=0)
        else:
            sketch = np.zeros((512,320,3))
            # sketch = 255*sketch
            sketch = np.asarray(sketch[:,:,0]/255,dtype=np.uint8)
            sketch = np.expand_dims(sketch,axis=2)
            sketch = np.expand_dims(sketch,axis=0)
        return sketch

    def make_stroke(self, pts):
        if len(pts)>0:
            stroke = np.zeros((512,320,3))
            for pt in pts:
                c = pt['color'].lstrip('#')
                color = tuple(int(c[i:i+2], 16) for i in (0, 2 ,4))
                cv2.line(stroke,pt['prev'],pt['curr'],color,4)
            stroke = stroke/127.5 - 1
            stroke = np.expand_dims(stroke,axis=0)
        else:
            stroke = np.zeros((512,320,3))
            stroke = stroke/127.5 - 1
            stroke = np.expand_dims(stroke,axis=0)
        return stroke

    def arrange(self):
        image = np.asarray((self.mat_img[0]+1)*127.5,dtype=np.uint8)
        image=cv2.cvtColor(image,cv2.COLOR_RGB2BGR)
        if len(self.scene.mask_points)>0:
            for pt in self.scene.mask_points:
                cv2.line(image,pt['prev'],pt['curr'],(255,255,255),12)
        if len(self.scene.stroke_points)>0:
            for pt in self.scene.stroke_points:
                c = pt['color'].lstrip('#')
                color = tuple(int(c[i:i+2], 16) for i in (0, 2 ,4))
                color = (color[2],color[1],color[0])
                cv2.line(image,pt['prev'],pt['curr'],color,4)
        if len(self.scene.sketch_points)>0:
            for pt in self.scene.sketch_points:
                cv2.line(image,pt['prev'],pt['curr'],(0,0,0),1)
        cv2.imwrite('tmp.jpg',image)
        image = QPixmap('tmp.jpg')
        self.scene.history.append(3)
        self.scene.addPixmap(image)

    def save_img(self):
        if type(self.output_img):
            fileName, _ = QFileDialog.getSaveFileName(self, "Save File",
                    QDir.currentPath())
            cv2.imwrite(fileName+'.jpg',self.output_img)

    def undo(self):
        self.scene.undo()

    def clear(self):
        self.scene.reset_items()
        self.scene.reset()
        if type(self.image):
            self.scene.addPixmap(self.image)

if __name__ == '__main__':
    opt = TestOptions().parse(save=False)
    model = create_model(opt)
    app = QApplication(sys.argv)
    ex = Ex(model)
    sys.exit(app.exec_())


"""
Part 2: This is the simplest version of viterbi that doesn't do anything special for unseen words
but it should do better than the baseline at words with multiple tags (because now you're using context
to predict the tag).
"""
import math

def viterbi_1(train, test):
    '''
    input:  training data (list of sentences, with tags on the words)
            test data (list of sentences, no tags on the words)
    output: list of sentences with tags on the words
            E.g., [[(word1, tag1), (word2, tag2)], [(word3, tag3), (word4, tag4)]]
    '''
    '''
    Given an input word sequence W, our goal is to find the tag sequence T that maximizes

    log[P(T|W)] ∝ ∑(i=1 to n){log[P(w_i|t_i)]} + ∑(k=1 to n){log[P(t_k|t_(k-1))]}

    P_t(t_k|t_k−1) transition probabilities, P_e(w_i|t_i) emission probabilities
    '''
    # ------------------- ESTIMATION -------------------
    # Use helper function "counting" to count occurrences of tags, tag pairs, tag/word pairs.
    end = math.ceil(len(train)/4)
    #[tag_count,transition_count,emission_count] = counting(train[0:end])
    [tag_count,transition_count,emission_count] = counting(train)

    # Use helper function "estimation" to estimate transition and emission probabilities
    [p_t,p_e] = estimation(tag_count,transition_count,emission_count)

    # ------------------- DECODING - VITERBI ALGORITHM -------------------
    #m = len(tag_count)
    print("\n-------- DECODING --------\n")
    output = []
    tags = []
    for tag in tag_count.keys():
        if tag not in ["START","END"]:
            tags.append(tag)
    print("Tags: ",tags,"\n")
    for sentence in test:
        n = len(sentence)
        # Initialize Trellis
        trellis = []
        # Fill first column with initial probabilities
        temp = []
        word1 = sentence[0]
        for tag in tags:
            if tag not in p_t["START"]:
                ps = p_t["START"]["UNKNOWN"]
            else:
                ps = p_t["START"][tag]
            if word1 not in p_e:
                pe = p_e["UNKNOWN"][tag]
            elif tag not in p_e[word1]:
                pe = p_e[word1]["UNKNOWN"]
            else:
                pe = p_e[word1][tag]
            temp.append((ps+pe,tag))
        trellis.append(temp)
        for k in range(1,n):
            temp = []
            word = sentence[k]
            for tag1 in tags:
                t1 = tags.index(tag1)
                for tag2 in tags:
                    t2 = tags.index(tag2)
                    prev_v = trellis[k-1][t1][0]
                    if tag2 not in p_t[tag1]:
                        pt = p_t[tag1]["UNKNOWN"]
                    else:
                        pt = p_t[tag1][tag2]
                    if word not in p_e:
                        pe = p_e["UNKNOWN"][tag1]
                    elif tag1 not in p_e[word]:
                        pe = p_e[word]["UNKNOWN"]
                    else:
                        pe = p_e[word][tag1]
                    # current v
                    curr_v = prev_v + pt + pe
                    curr_tuple = (curr_v,tag1)
                    if t1 == 0:
                        temp.append(curr_tuple)
                    elif temp[t2][0] < curr_v:
                        temp[t2] = curr_tuple
            trellis.append(temp)
        # Finished the trellis, pick the best tag in the final column
        output_tags = []
        end = trellis[len(trellis)-1]
        max_tag_idx = end.index((max(end, key=lambda pair: pair[0])))
        output_tags.append(tags[max_tag_idx])

        prev_tag = max(end, key=lambda pair: pair[0])
        for i in range(len(trellis)-1, 0, -1):
            prev_tag = trellis[i - 1][tags.index(prev_tag[1])]
            output_tags.insert(0, prev_tag[1])

        max_start_tag = max(trellis[0], key=lambda pair: pair[0])[1]
        output_tags[0] = max_start_tag

        output_sentence = []
        for word_idx in range(len(sentence)):
            output_sentence.append((sentence[word_idx],output_tags[word_idx]))

        output.append(output_sentence)

    return output

'''
Counts occurrences of tags, tag pairs, tag/word pairs, given some train data
'''
def counting(train):
    print("\n-------- COUNTING --------\n")
    tag_count = {}
    transition_count = {}
    emission_count = {}
    for sentence in train:
        #tag_count["START"] = tag_count.get("START",0) + 1
        for word_idx in range(len(sentence)-1):
            word = sentence[word_idx][0]
            tag = sentence[word_idx][1]
            # (i) count tag occurences
            tag_count[tag] = tag_count.get(tag,0) + 1
            # (ii) count tag transitions - # ex: {"NOUN_VERB":15,"NOUN_ADV":14}
            next_tag = sentence[word_idx+1][1]
            if tag not in transition_count:
                transition_count[tag] = {}
            curr_count = transition_count[tag]
            curr_count[next_tag] = curr_count.get(next_tag,0) + 1
            # (iii) count word tag pairs
            if word not in emission_count:
                emission_count[word] = {}
            word_tags = emission_count[word]
            word_tags[tag] = word_tags.get(tag,0) + 1
    return [tag_count,transition_count,emission_count]

'''
Estimates transition and emission probabilities, given counts
Smoothes the probabilities and takes the log
'''
def estimation(tag_count,transition_count,emission_count):
    print("\n-------- ESTIMATION --------\n")
    # (i) estimation transition probabilities P(t_k|t_(k-1))
    '''
        P(t_k|t_(k-1)) = [count(t_(k-1),t_k)+α]/[n+α(V+1)]. Where,
        α = Laplace Parameter = smoothing
        n = count(t_(k-1)) = tag_count[prev_tag]
        V = number of tags = len(tag_count)
    '''
    smoothing = 0.0000001       # Set Laplace parameter
    p_t = {}                # Dictionary to store tag pair probabilities
    v = len(tag_count)
    max_n = 0
    for tag in transition_count:
        p_t[tag] = {}
        n = tag_count[tag]
        for tag2 in transition_count[tag]:
            count = transition_count[tag][tag2]
            p_t[tag][tag2] = math.log((count + smoothing)/(n + smoothing*v))
            # "UNKNOWN" denotes all the words we have not encountered in training data
        p_t[tag]["UNKNOWN"] = math.log(smoothing/(n + smoothing*v))

    #
    # (ii) estimation emission probabilities P_e(w_i|t_i)
    '''
        P_e(w_i|t_i) = [count(t_i,w_i) + α]/[n + α(V+1)]. Where,
        α = Laplace Parameter = smoothing
        n = count(t_i) = tag_count[tag]
        V = number of tags = len(tag_count)
    '''
    # Emission count = {'DET':{'the': 4362, 'an': 123}}
    smoothing = 0.0000001     # Set Laplace parameter
    p_e = {}                # Dictionary to store tag pair probabilities
    for word in emission_count:
        p_e[word] = {}
        total = 0
        for tag in emission_count[word]:
            n = tag_count[tag]
            count = emission_count[word][tag]
            total += count
            p_e[word][tag] = math.log((count + smoothing)/(n + smoothing*(v+1)))
        p_e[word]["UNKNOWN"] = math.log((smoothing)/(total + smoothing*(v+1)))
    p_e["UNKNOWN"] = {}
    max_n = 0
    for tag in tag_count:
        n = tag_count[tag]
        if n>max_n:
            max_n = n
        # "UNKNOWN" denotes all the words we have not encountered in training data
        p_e["UNKNOWN"][tag] = math.log(smoothing/(n + smoothing*(v+1)))
    p_e["UNKNOWN"]["UNKNOWN"] = math.log(smoothing/(max_n + smoothing*(v+1)))
    # Return transition & emission probabilities
    return [p_t,p_e]


import logging

def setup_logger(logger_name, log_file, level=logging.INFO):
    log_setup = logging.getLogger(logger_name)
    formatter = logging.Formatter('%(levelname)s: %(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
    fileHandler = logging.FileHandler(log_file, mode='a', encoding='utf-8')
    fileHandler.setFormatter(formatter)
    log_setup.setLevel(level)
    log_setup.addHandler(fileHandler)

def setup_dual_logger(logger_name, log_file, level=logging.INFO):
    log_setup = logging.getLogger(logger_name)
    formatter = logging.Formatter('%(levelname)s: %(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
    fileHandler = logging.FileHandler(log_file, mode='a', encoding='utf-8')
    fileHandler.setFormatter(formatter)
    streamHandler = logging.StreamHandler()
    streamHandler.setFormatter(formatter)
    log_setup.setLevel(level)
    log_setup.addHandler(fileHandler)
    log_setup.addHandler(streamHandler)

def logger(msg, level, logfile):
    if logfile == 'a'   : log = logging.getLogger('activity_log')
    if logfile == 'd'   : log = logging.getLogger('download_log')
    if level == 'info'    : log.info(msg)
    if level == 'warning' : log.warning(msg)
    if level == 'error'   : log.error(msg)

def plogger(msg, level, logfile):
    logger(msg, level, logfile)
    print(msg)

def blogger(msg, level, logfile, bar):
    logger(msg, level, logfile)
    bar.text(msg)


from math import *

class Origin():
     def __init__(self, x, y, deg, sx, sy):
          self.x = x
          self.y = y
          rad = radians(deg)
          self.a = cos(rad)
          self.b = sin(rad)
          self.sx = sx
          self.sy = sy

     def move(self, x, y):
          self.x += x
          self.y += y

     def rotate(self, deg):
          rad = radians(deg);
          a = cos(rad); b = sin(rad)
          self_a = self.a
          self.a = self_a*a - self.b*b
          self.b = self.b*a + self_a*b

     def scale(self, sx, sy):
          if sx is not 0 : self.sx *= sx
          if sy is not 0 : self.sy *= sy

     def transform(self, x, y, deg):
          self.x += x
          self.y += y
          rad = radians(deg);
          a = cos(rad); b = sin(rad)
          self_a = self.a
          self.a = self_a*a - self.b*b
          self.b = self.b*a + self_a*b

class Vertices():
     def __init__(self, vertices):
          self.count = len(vertices)//2
          self.pos   = vertices
          self.tan   = [0.0]*self.count*2
          self.siz   = [0.0]*self.count*2
          v=self.pos; t=self.tan; s=self.siz;
          print(vertices)
          for i in range(self.count):
               tx       = v[i*2]   - v[i*2-2]
               ty       = v[i*2+1] - v[i*2-1]
               size     = sqrt(tx**2 + ty**2)
               s[i*2]   = size
               t[i*2]   = tx/size
               t[i*2+1] = ty/size

class Bitmap():
     def __init__(self, width, height):
          self.width  = width
          self.height = height
          self.polys  = None
          self.items  = None

class ColorMap():
     def __init__(self, mlist, mmin, mmax):
          self.list = mlist
          self.min  = mmin
          self.max  = mmax

class CanvasEx():
     def __init__(self, canvas):
          print(canvas)
          hwidth  = int(canvas["width"])//2
          hheight = int(canvas["height"])//2
          self.canvas = canvas
          self.ox = hwidth
          self.oy = hheight
          self.sx = hheight
          self.sy = hheight
          self.bgr = 0x22
          self.bgg = 0x33
          self.bgb = 0x55
          color = self.bgr * 0x010000 +\
                  self.bgg * 0x000100 +\
                  self.bgb * 0x000001
          self.canvas["bg"]= "#%06X"%(color)

     def updatebg(self):
          color = r * 0x010000 +\
                  g * 0x000100 +\
                  b * 0x000001
          self.canvas["bg"]= "#%06X"%(color)

     def setbg(self, r,g,b):
          if r < 0 : r = 0;
          if 0xff < r : r = 0xff
          if g < 0 : g = 0;
          if 0xff < g : g = 0xff
          if b < 0 : b = 0;
          if 0xff < b : b = 0xff
          self.bgr = r
          self.bgg = g
          self.bgb = b
          color = r * 0x010000 +\
                  g * 0x000100 +\
                  b * 0x000001
          self.canvas["bg"]= "#%06X"%(color)

     def viewport(self, ox, oy, sx, sy):
          self.ox = ox
          self.oy = oy
          self.sx = sx
          self.sy = sy

     def coordinate(self, vertices, offset, count, origin):
          count = len(vertices)//2
          x = origin.x
          y = origin.y
          a = origin.a
          b = origin.b
          coords = [0]*2*count
          for i in range(count):
               Px  = vertices[i*2 + offset]*origin.sx
               Py  = vertices[i*2 + offset +1]*origin.sy
               Pgx = a*Px - b*Py + x
               Pgy = b*Px + a*Py + y
               coords[i*2]   = self.ox + Pgx* self.sx
               coords[i*2+1] = self.oy - Pgy* self.sy
          return coords

     def deleteItem(self, item):
          self.canvas.delete(item)

     def createPolygon(self, vertices, offset, count, origin, color):
          coords = self.coordinate(vertices, offset, count, origin)
          return self.canvas.create_polygon(coords, fill=color)

     def coordsPolygon(self, item, vertices, offset, count, origin):
          coords = self.coordinate(vertices, offset, count, origin)
          self.canvas.coords(item, coords)

     def createLineLoop(self, vertices, offset, count, origin, color):
          coords = self.coordinate(vertices, offset, count, origin)
          coords += [coords[0], coords[1]]
          return self.canvas.create_line(coords, fill=color)

     def coordsLineLoop(self, item, vertices, offset, count, origin):
          coords = self.coordinate(vertices, offset, count, origin)
          coords += [coords[0], coords[1]]
          self.canvas.coords(item, coords)

     def createBitmap(self, bitmap, wsize, hsize,
                      width, height, origin, colormap, outline):

          mbitmap = Bitmap(width, height)
          if colormap is None:
               colormap = ColorMap(['white','red'], 0, 1)
          if outline is None:
               outline = 'black'

          index = 0
          block_width  = 2*wsize/width
          block_height = 2*hsize/height
          mbitmap.polys = []
          for row in range(height):
               for cul in range(width):
                    left      = -wsize + block_width  * (cul)
                    right     = -wsize + block_width  * (cul+1)
                    top       =  hsize - block_height * (row)
                    bottom    =  hsize - block_height * (row+1)
                    mbitmap.polys += \
                         [left, top, left, bottom, right, bottom, right, top]
                    index += 1

          coords = self.coordinate(mbitmap.polys,0,index,origin)

          mbitmap.items = [0]*width*height
          index = 0
          for row in range(height):
               for cul in range(width):
                    colorid = max(colormap.min, min(bitmap[index], colormap.max))
                    color = colormap.list[colorid]
                    item = self.canvas.create_polygon(coords[index*8:index*8+8],
                         fill=color, outline=outline)
                    mbitmap.items[index] = item
                    index += 1

          return mbitmap

     def coordsBitmap(self, bitmap, origin):
          count  = bitmap.width*bitmap.height
          coords = self.coordinate(bitmap.polys, 0, count*4, origin)
          for index in range(count):
               self.canvas.coords(bitmap.items[index],
                    coords[index*8:index*8+8])

     def colorBitmap(self, bitmap, colormap):
          count  = bitmap.width*bitmap.height
          if colormap is None:
               colormap = Colormap(['white','black'], 0, 1)
          for index in range(count):
               colorid = max(colormap.min,min(bitmap[index], colormap.max))
               color = colormap[colorid]
               self.canvas.itemconfig(bitmap.items[index], fill=color)

     def deleteBitmap(self, bitmap):
          for item in bitmap.items:
               self.canvas.delete(item)


from collections import OrderedDict
import heterocl as hcl
import heterocl.tvm as tvm
import numpy as np
import hlib
from ..utils import *
from .op import *

dtype = hcl.Float()

sum = hcl.reducer(0, lambda x, y: x + y, dtype)
max = hcl.reducer(-1, lambda x, y: tvm.make.Max(x, y), dtype)
_all = hcl.reducer(True, lambda x, y: x & y, bool)

def simplify(expr):
    return tvm.ir_pass.Simplify(expr) if isinstance(expr, tvm.expr.Expr) else expr

def pad(data, pad_before, pad_after=None, pad_value=0.0, name="pad"):
    n = len(data.shape)
    pad_after = pad_after if pad_after else pad_before
    if len(pad_before) != n:
        raise ValueError(
            "Input dimension and pad_before dismatch : %d vs %d" %
            (n, len(pad_before)))
    if len(pad_after) != n:
        raise ValueError(
            "Input dimension and pad_after dismatch : %d vs %d" %
            (n, len(pad_after)))
    out_shape = tuple(
        tvm.ir_pass.Simplify(
            (data.shape[i] + tvm.const(pad_before[i] + pad_after[i]))
        ) for i in range(n))
    pad_value = pad_value if isinstance(pad_value, tvm.expr.Expr) else tvm.const(pad_value, data.dtype)

    def _pad(*indices):
        not_zero = []
        index_tuple = []
        for i in range(n):
            if pad_before[i] == 0 and pad_after[i] == 0:
                index_tuple.append(indices[i])
            else:
                index_tuple.append(indices[i] - pad_before[i])
                not_zero.append(indices[i] >= pad_before[i])
                not_zero.append(indices[i] < data.shape[i] + pad_before[i])
        if not_zero:
            not_zero = tvm.all(*not_zero)
            return tvm.select(not_zero, data[tuple(index_tuple)], pad_value)
        return data[tuple(index_tuple)]

    return hcl.compute(out_shape, _pad, name=name)

def relay_pad(data, pad_width, pad_value=0.0,
              pad_mode='constant', frontend='keras'):
    pad_before = []
    pad_after = []
    for padded in pad_width:
        pad_before.append(tvm_to_primitive(padded[0]))
        pad_after.append(tvm_to_primitive(padded[1]))
    return pad(data, pad_before, pad_after, pad_value)

def get_pad_tuple(padding, kernel):
    if isinstance(padding, (tuple, list)):
        pad_h = padding[0] * 2
        pad_w = padding[1] * 2
    elif isinstance(padding, int):
        pad_h = pad_w = padding * 2
    elif padding == "VALID":
        pad_h = 0
        pad_w = 0
    elif padding == "SAME":
        pad_h = kernel[0] - 1
        pad_w = kernel[1] - 1
    else:
        raise ValueError("Unknown padding option %s" % padding)
    pad_top = ((pad_h + 1) // 2)
    pad_left = ((pad_w + 1) // 2)
    return pad_top, pad_left, pad_h - pad_top, pad_w - pad_left

def dilate(data, strides, name="DilatedInput"):
    n = len(data.shape)
    if len(strides) != n:
        raise ValueError(
            "data dimension and strides size dismatch : %d vs %d" %
            (n, len(strides)))

    out_shape = tuple(
        simplify((data.shape[i] - 1) * strides[i] + 1) for i in range(n))

    def _dilate(*indices):
        not_zero = []
        index_tuple = []
        for i in range(n):
            if strides[i] != 1:
                index_tuple.append(indices[i] / strides[i])
                not_zero.append((indices[i] % strides[i]).equal(0))
            else:
                index_tuple.append(indices[i])
        if not_zero:
            not_zero = tvm.api.all(*not_zero)
            if not_zero:
                return data(*index_tuple)
            else:
                return hcl.cast(data.dtype, 0.0)
        return data(*index_tuple)
    return hcl.compute(out_shape, _dilate, name=name)

def conv2d_transpose(
        data,
        kernel,
        strides=[1, 1],
        padding=[0, 0],
        out_dtype=None):
    if out_dtype is None:
        out_dtype = data.dtype
    """Implementation of conv2d transpose"""
    batch, in_c, in_h, in_w = data.shape
    _, out_c, filter_h, filter_w = kernel.shape
    stride_h, stride_w = strides
    # dilate stage
    DilatedInput = dilate(
        data, [1, 1, stride_h, stride_w], name='DilatedInput')
    # padding stage
    fpad_top, fpad_left, fpad_bottom, fpad_right = get_pad_tuple(
        padding, (filter_h, filter_w))
    bpad_top = filter_h - 1 - fpad_top
    bpad_bottom = filter_h - 1 - fpad_bottom
    bpad_left = filter_w - 1 - fpad_left
    bpad_right = filter_w - 1 - fpad_right
    PaddedInput = pad(DilatedInput,
                      [0, 0, bpad_top, bpad_left],
                      [0, 0, bpad_bottom, bpad_right],
                      name='PaddedInput')
    # convolution stage
    out_c = simplify(out_c)
    out_h = simplify((in_h - 1) * stride_h - fpad_top - fpad_bottom + filter_h)
    out_w = simplify((in_w - 1) * stride_w - fpad_left - fpad_right + filter_w)
    dc = hcl.reduce_axis((0, in_c), name='dc')
    dh = hcl.reduce_axis((0, filter_h), name='dh')
    dw = hcl.reduce_axis((0, filter_w), name='dw')

    return hcl.compute(
        (batch, out_c, out_h, out_w),
        lambda b, c, h, w: hcl.sum(
            PaddedInput[b, dc, h + dh, w + dw].astype(out_dtype) *
            kernel[dc, c, filter_h - 1 - dh,
                   filter_w - 1 - dw].astype(out_dtype),
            axis=[dc, dh, dw]), name="conv2d_transpose")

def conv2d(
        Input,
        Filter,
        Bias=None,
        strides=[1, 1],
        padding=[0, 0],
        dilation=[1, 1],
        groups=1,
        channels=1,
        kernel_size=[1, 1],
        data_layout='NCHW',
        kernel_layout='OIHW',
        out_layout='',
        name="conv2d",
        out_dtype=None,
        frontend='keras'):
    p = []
    s = []
    d = []
    for i in range(len(padding)):
        p.append(tvm_to_primitive(padding[i]))
    for i in range(len(strides)):
        s.append(tvm_to_primitive(strides[i]))
        d.append(tvm_to_primitive(dilation[i]))
    strides = s
    padding = p
    dilation = d
    channels = tvm_to_primitive(channels)
    groups = tvm_to_primitive(groups)
    if out_dtype is None or out_dtype == '':
        out_dtype = Input.dtype
    if data_layout == 'NCHW':
        out = conv2d_nchw(
            Input,
            Filter,
            strides,
            padding,
            dilation,
            name='conv2d',
            groups=groups,
            out_dtype=out_dtype)
    elif data_layout == 'NHWC':
        out = conv2d_nhwc(
            Input,
            Filter,
            strides,
            padding,
            dilation,
            name='conv2d',
            groups=groups,
            out_dtype=out_dtype)
    elif data_layout == 'HWCN':
        out = conv2d_hwcn(
            Input,
            Filter,
            strides,
            padding,
            dilation,
            name='conv2d',
            groups=groups,
            out_dtype=out_dtype)
    else:
        raise ValueError("not support this layout {} yet".format(data_layout))
    return out

def conv2d_nhwc(
        Input,
        Filter,
        strides=[1, 1],
        padding=[1, 1],
        dilation=[1, 1],
        out_dtype='float',
        groups=1,
        name='conv2d'):
    assert isinstance(strides, int) or len(strides) == 2
    assert isinstance(dilation, int) or len(dilation) == 2
    if out_dtype is None:
        out_dtype = Input.dtype
    if isinstance(strides, int):
        stride_h = stride_w = strides
    else:
        stride_h, stride_w = strides

    if isinstance(dilation, int):
        dilation_h = dilation_w = dilation
    else:
        dilation_h, dilation_w = dilation

    batch, in_height, in_width, in_channel = Input.shape
    kernel_h, kernel_w, channel, num_filter = Filter.shape

    dilated_kernel_h = (kernel_h - 1) * dilation_h + 1
    dilated_kernel_w = (kernel_w - 1) * dilation_w + 1
    pad_top, pad_left, pad_down, pad_right = get_pad_tuple(
        padding, (dilated_kernel_h, dilated_kernel_w))
    out_channel = num_filter
    out_height = simplify(
        (in_height -
         dilated_kernel_h +
         pad_top +
         pad_down) //
        stride_h +
        1)
    out_width = simplify(
        (in_width -
         dilated_kernel_w +
         pad_left +
         pad_right) //
        stride_w +
        1)
    pad_before = [0, pad_top, pad_left, 0]
    pad_after = [0, pad_down, pad_right, 0]
    temp = pad(Input, pad_before, pad_after, name="temp")
    rc = hcl.reduce_axis(0, in_channel, name='rc')
    ry = hcl.reduce_axis(0, kernel_h, name='ry')
    rx = hcl.reduce_axis(0, kernel_w, name='rx')
    return hcl.compute(
        (batch, out_height, out_width, out_channel),
        lambda nn, yy, xx, ff: hcl.sum(
            temp[nn, yy * stride_h + ry * dilation_h,
                 xx * stride_w + rx * dilation_w, rc] *
            Filter[ry, rx, rc, ff], axis=[ry, rx, rc],
            name=name, dtype=out_dtype))

def conv2d_nchw(
        Input,
        Filter,
        strides=[1, 1],
        padding=[0, 0],
        dilation=[1, 1],
        out_dtype=None,
        groups=1,
        name='conv2d'):
    if out_dtype is None or out_dtype == '':
        out_dtype = Input.dtype
    assert isinstance(strides, int) or len(strides) == 2
    assert isinstance(dilation, int) or len(dilation) == 2
    if isinstance(strides, int):
        stride_h = stride_w = strides
    else:
        stride_h, stride_w = strides

    if isinstance(dilation, int):
        dilation_h = dilation_w = dilation
    else:
        dilation_h, dilation_w = dilation

    if groups > 1:
        shape = Filter.shape
        new_shape = (shape[0], groups, shape[2], shape[3])
        Filter = hcl.compute(new_shape, lambda o, i, h, w: Filter[o, 0, h, w])
    batch, in_channel, in_height, in_width = Input.shape
    num_filter, channel, kernel_h, kernel_w = Filter.shape
    # compute the output shape
    dilated_kernel_h = (kernel_h - 1) * dilation_h + 1
    dilated_kernel_w = (kernel_w - 1) * dilation_w + 1
    pad_top, pad_left, pad_down, pad_right = get_pad_tuple(
        padding, (dilated_kernel_h, dilated_kernel_w))
    out_channel = num_filter
    out_height = simplify(
        (in_height -
         dilated_kernel_h +
         pad_top +
         pad_down) //
        stride_h +
        1)
    out_width = simplify(
        (in_width -
         dilated_kernel_w +
         pad_left +
         pad_right) //
        stride_w +
        1)
    # compute graph
    pad_before = [0, 0, pad_top, pad_left]
    pad_after = [0, 0, pad_down, pad_right]
    temp = pad(Input, pad_before, pad_after, name="pad_temp")
    if groups > 1:
        rc = hcl.reduce_axis(0, channel / groups, name='rc')
    else:
        rc = hcl.reduce_axis(0, channel, name='rc')
    ry = hcl.reduce_axis(0, kernel_h, name='ry')
    rx = hcl.reduce_axis(0, kernel_w, name='rx')
    if groups > 1:
        return hcl.compute(
            (batch, out_channel, out_height, out_width),
            lambda nn, ff, yy, xx: hcl.sum(
                temp[nn, ff % groups, yy * stride_h + ry * dilation_h,
                     xx * stride_w + rx * dilation_w] *
                Filter[ff, rc, ry, rx],
                axis=[rc, ry, rx], dtype=out_dtype), name=name, dtype=out_dtype)
    return hcl.compute(
        (batch, out_channel, out_height, out_width),
        lambda nn, ff, yy, xx: hcl.sum(
            temp[nn, rc, yy * stride_h + ry * dilation_h,
                 xx * stride_w + rx * dilation_w] *
            Filter[ff, rc, ry, rx],
            axis=[rc, ry, rx], dtype=out_dtype), name=name, dtype=out_dtype)

def conv2d_hwcn(
        Input,
        Filter,
        strides=[1, 1],
        padding=[0, 0],
        dilation=[1, 1],
        out_dtype=None,
        groups=1,
        name='conv2d'):
    if out_dtype is None:
        out_dtype = Input.dtype
    assert isinstance(strides, int) or len(strides) == 2
    assert isinstance(dilation, int) or len(dilation) == 2

    if isinstance(strides, int):
        stride_h = stride_w = strides
    else:
        stride_h, stride_w = strides

    if isinstance(dilation, int):
        dilation_h = dilation_w = dilation
    else:
        dilation_h, dilation_w = dilation

    in_height, in_width, in_channel, batch = Input.shape
    kernel_h, kernel_w, channel, num_filter = Filter.shape
    # compute the output shape
    dilated_kernel_h = (kernel_h - 1) * dilation_h + 1
    dilated_kernel_w = (kernel_w - 1) * dilation_w + 1
    pad_top, pad_left, pad_down, pad_right = get_pad_tuple(
        padding, (dilated_kernel_h, dilated_kernel_w))
    out_channel = num_filter
    out_height = simplify(
        (in_height -
         dilated_kernel_h +
         pad_top +
         pad_down) //
        stride_h +
        1)
    out_width = simplify(
        (in_width -
         dilated_kernel_w +
         pad_left +
         pad_right) //
        stride_w +
        1)
    pad_before = [pad_top, pad_left, 0, 0]
    pad_after = [pad_down, pad_right, 0, 0]
    temp = pad(Input, pad_before, pad_after, name="temp")
    rc = hcl.reduce_axis(0, in_channel, name='rc')
    ry = hcl.reduce_axis(0, kernel_h, name='ry')
    rx = hcl.reduce_axis(0, kernel_w, name='rx')
    return tvm.compute(
        (out_height, out_width, out_channel, batch),
        lambda yy, xx, ff, nn: hcl.sum(
            temp[yy * stride_h + ry * dilation_h, xx * stride_w + rx * dilation_w,
                 rc, nn] * Filter[ry, rx, rc, ff], axis=[ry, rx, rc],
            dtype=out_dtype),
        name=name)

def conv2d_nchw_old(
        Input,
        Filter,
        name="conv2d",
        stride=[1, 1],
        padding=[[0, 0], [0, 0]]):
    out_dtype = Input.dtype
    batch, in_channel, in_height, in_width = Input.shape
    num_filter, channel, kernel_h, kernel_w = Filter.shape
    stride_h, stride_w = stride
    [pad_top, pad_left], [pad_down, pad_right] = padding
    # compute the output shape
    out_channel = num_filter
    out_height = simplify(
        (in_height -
         kernel_h +
         pad_top +
         pad_down) //
        stride_h +
        1)
    out_width = simplify(
        (in_width -
         kernel_w +
         pad_left +
         pad_right) //
        stride_w +
        1)
    # compute graph
    pad_before = [0, 0, pad_top, pad_left]
    pad_after = [0, 0, pad_down, pad_right]
    if padding != [[0, 0], [0, 0]]:
        Input = pad(Input, pad_before, pad_after)
    rc = hcl.reduce_axis(0, in_channel)
    ry = hcl.reduce_axis(0, kernel_h)
    rx = hcl.reduce_axis(0, kernel_w)
    return hcl.compute(
        (batch, out_channel, out_height, out_width),
        lambda nn, ff, yy, xx: sum(
            Input[nn, rc, yy * stride_h + ry, xx * stride_w + rx] *
            Filter[ff, rc, ry, rx],
            axis=[rc, ry, rx]),
        name=name,
        attrs=OrderedDict([
            ('p', kernel_h),
            ('q', kernel_w),
            ('in_num', in_channel),
            ('out_num', out_channel),
            ('out_img_w', out_width),
            ('out_img_h', out_height),
            ('cin_dtype', tvm.make.StringImm(Input.dtype)),
            ('filter_dtype', tvm.make.StringImm(Filter.dtype)),
            ('app_name', tvm.make.StringImm('cnn'))]))

def dense(data, weight, units=None, out_dtype='', bias=None, name="dense"):
    assert len(
        data.shape) == 2 and len(
        weight.shape) == 2, "only support 2-dim dense"
    if bias is not None:
        assert len(bias.shape) == 1
    batch, in_dim = data.shape
    out_dim, _ = weight.shape
    k = hcl.reduce_axis(0, in_dim)
    attrs = OrderedDict([
        ('k', in_dim),
        ('j', out_dim),
        ('i', batch),
        ('app_name', tvm.make.StringImm('mm'))])
    matmul = hcl.compute((batch, out_dim), lambda i, j: sum(
        data[i, k] * weight[j, k], axis=k), name, attrs=attrs)
    if bias is not None:
        matmul = hcl.compute(
            (batch, out_dim),
            lambda i, j: matmul[i, j] + bias[j],
            name=name,
            attrs=attrs)
    return matmul

def bias_add(data, bias, axis=-1, name='bias_add'):
    def _broadcast(shape, *indices):
        axes = []
        indices = indices[0]
        for i in range(len(shape)):
            if shape[i] == 1:
                axes.append(0)
            else:
                axes.append(indices[i])
        return tuple(axes)
    data_len = len(data.shape)
    if axis < 0:
        axis += data_len
    num_newaxis = data_len - axis - 1
    bias = expand_dims(bias, len(bias.shape), num_newaxis)
    bias = expand_dims(bias, 0, axis)
    return hcl.compute(
        data.shape, lambda *x: data[x] + bias[_broadcast(bias.shape, x)], name=name)

def squeeze(data, axis=None, name='squeeze'):
    if axis is None:
        axis = []
        for i in range(len(data.shape)):
            if data.shape[i] == 1:
                axis.append(i)
    else:
        axis = [tvm_to_primitive(x) for x in axis]
    l_orig = len(data.shape)
    new_shape = []
    for i in range(len(data.shape)):
        if not i in axis:
            new_shape.append(data.shape[i])

    def _ind(axis, l_orig, *indices):
        indices = indices[0]
        new_shape = []
        idx = 0
        for i in range(l_orig):
            if i not in axis:
                new_shape.append(indices[idx])
                idx = idx + 1
            else:
                new_shape.append(0)
        return tuple(new_shape)
    return hcl.compute(
        tuple(new_shape), lambda *x: data[_ind(axis, l_orig, x)], name=name)

def split(data, indices_or_sections, axis=0, name='split'):
    def split_idx(start, axis, *indices):
        indices = indices[0]
        new_ind = []
        for i in range(len(indices)):
            if i == axis:
                new_ind.append(start + indices[i])
            else:
                new_ind.append(indices[i])
        return tuple(new_ind)
    try:
        if not hasattr(indices_or_sections, "value"):
            _list = []
            for section in indices_or_sections:
                _list.append(tvm_to_primitive(section))
            indices_or_sections = _list
    except BaseException:
        _list = []
        if isinstance(indices_or_sections, int):
            pass
        else:
            for section in indices_or_sections:
                _list.append(tvm_to_primitive(section))
            indices_or_sections = _list
    if not isinstance(indices_or_sections, list):
        if hasattr(indices_or_sections, "value"):
            indices_or_sections = indices_or_sections.value
        assert (axis >= 0 & axis < len(data.shape)
                ), "axis not in bounds of shape"
        assert(
            data.shape[axis] %
            indices_or_sections == 0), "indices doesn't divide equally"
        new_shape = list(data.shape)
        intval = new_shape[axis] // indices_or_sections
        new_shape[axis] = intval
        out = []
        for i in range(indices_or_sections):
            out.append(hcl.compute(
                tuple(new_shape), lambda *x: data[split_idx(i * intval, axis, x)], name=name))
    else:
        new_shape = []
        for s in range(len(indices_or_sections)):
            if hasattr(indices_or_sections[s], "value"):
                indices_or_sections[s] = indices_or_sections[s].value
        for i in range(len(indices_or_sections) + 1):
            new_shape.append(list(data.shape))
        start = 0
        axis_width = []
        for i in range(len(indices_or_sections)):
            ax = indices_or_sections[i]
            new_shape[i][axis] = ax - start
            axis_width.append(start)
            start = ax
        ax = new_shape[-1][axis]
        new_shape[-1][axis] = ax - start
        axis_width.append(start)
        out = []
        for i in range(len(indices_or_sections) + 1):
            out.append(hcl.compute(tuple(
                new_shape[i]), lambda *x: data[split_idx(axis_width[i], axis, x)], name=name))
    return tuple(out)

def concatenate(*data_tup, axis=1, name='concatenate', frontend='keras'):
    idx_start = [0]
    axis_len = 0
    for i in range(len(data_tup)):
        idx_start.append(idx_start[i] + (data_tup[i]).shape[axis])
        axis_len = axis_len + (data_tup[i]).shape[axis]

    new_shape = list(data_tup[0].shape)
    new_shape[axis] = axis_len
    C = hcl.placeholder(tuple(new_shape))

    def concat(data, offset, *indices):
        orig_idx = list(indices[0])
        idx = list(indices[0])
        idx[axis] = idx[axis] + offset
        orig_idx = tuple(orig_idx)
        idx = tuple(idx)
        C[idx] = data[orig_idx]
    for i in range(len(data_tup)):
        hcl.mutate(data_tup[i].shape,
                   lambda *x: concat(data_tup[i], idx_start[i], x),
                   name=name)
    return C

def reduce_mult(l):
    result = 1
    for item in l:
        result = result * item
    return result

def reshape(data, newshape, name='reshape'):
    new_shape = []
    for i in range(len(newshape)):
        new_shape.append(tvm_to_primitive(newshape[i]))
    res_shape = []
    cur_shape = list(data.shape)
    idx = 0
    idx_n1 = -1
    for _ in range(len(new_shape)):
        new_idx = new_shape[idx]
        assert(new_idx > -5), "idx has to be greater than -5"
        if new_idx > 0:
            res_shape.append(new_idx)
        elif new_idx == 0:
            res_shape.append(cur_shape[idx])
        elif new_idx == -1:
            if not idx_n1 == -1:
                raise ValueError("no more than one -1 is allowed in newshape")
            idx_n1 = idx
        elif new_idx == -2:
            res_shape.extend(cur_shape[idx:])
        elif new_idx == -3:
            res_shape.append(cur_shape[idx] + cur_shape[idx + 1])
            idx = idx + 1
        elif new_idx == -4:
            assert False, "not implemented yet"
        idx = idx + 1
    if not idx_n1 == -1:
        res_shape.insert(idx_n1, reduce_mult(cur_shape) // reduce_mult(res_shape))
    assert(reduce_mult(cur_shape) == reduce_mult(res_shape)), "shape must contain same total product"
    cur_order = [1]
    res_order = [1]
    c_ord = len(cur_shape) - 1
    for i in range(len(cur_shape) - 1):
        cur_order.append(cur_shape[c_ord - i] * cur_order[i])
    r_ord = len(res_shape) - 1
    for i in range(len(res_shape) - 1):
        res_order.append(res_shape[r_ord - i] * res_order[i])

    def _reshape_idx(*indices):
        indices = indices[0]
        elm_idx = 0
        data_idx = []
        for i in range(len(indices)):
            elm_idx = indices[r_ord - i] * res_order[i] + elm_idx
        for i in range(len(cur_order)):
            data_idx.append((elm_idx // (cur_order[c_ord - i])) % cur_shape[i])
        return tuple(data_idx)
    return hcl.compute(
        tuple(res_shape), lambda *x: data[_reshape_idx(x)], name=name)

def batch_norm(
        data,
        gamma,
        beta,
        moving_mean,
        moving_var,
        axis=1,
        epsilon=10**-7,
        center=1,
        scale=1,
        name="batch_norm"):
    if axis < 0:
        axis = len(data.shape) - 1
    mred = []
    vred = []
    size = 1.0
    for i in range(len(data.shape)):
        if not i == axis:
            mred.append(hcl.reduce_axis(0, data.shape[i], "mred" + str(i)))
            vred.append(hcl.reduce_axis(0, data.shape[i], "vred" + str(i)))
            size = size * data.shape[i]
    new_shape = (data.shape[axis],)

    def insert_axis(axis, red, *indices):
        idx = []
        cur_red = 0
        for i in range(len(data.shape)):
            if i == axis:
                idx.append(indices[0])
            else:
                idx.append(red[cur_red])
                cur_red = cur_red + 1
        return tuple(idx)

    def get_axis(axis, *indices):
        indices = list(indices[0])
        return (indices[axis],)
    out = hcl.compute(data.shape, lambda *x: (data[x] - moving_mean[get_axis(axis, x)]) /
                    (hcl.sqrt(moving_var[get_axis(axis, x)] + epsilon)) * gamma[get_axis(axis, x)]
                    + beta[get_axis(axis, x)], name=name, dtype=data.dtype)
    return out, moving_mean, moving_var

def batch_matmul(x, y, name="batch_matmul"):
    out_shape = (x.shape[0], x.shape[1], y.shape[2])
    k = hcl.reduce_axis(0, x.shape[2], "k")
    return hcl.compute(out_shape, lambda b, m, n: hcl.sum(x[b,m,k] * y[b,n,k], axis = [k]), name=name, dtype = x.dtype)

def dropout(data, rate=0.5):
    data = hcl.compute(data.shape, lambda *x: data[x])
    mask = hcl.compute(data.shape, lambda *x: hcl.cast(dtype, 1))
    return data, mask

def max_pool(data, kernel, stride, padding=[[0, 0], [0, 0]], name="max_pool2d"):
    assert len(data.shape) == 4, "only support 4-dim pooling"
    assert len(stride) == 2, "only support 2-dim stride"
    kernel_height, kernel_width = kernel
    stride_height, stride_width = stride
    batch, channel, height, width = data.shape
    [pad_top, pad_left], [pad_down, pad_right] = padding
    pad_before = [0, 0, pad_top, pad_left]
    pad_after = [0, 0, pad_down, pad_right]
    if padding != [[0, 0], [0, 0]]:
        data = pad(data, pad_before, pad_after, pad_value=tvm.min_value(data.dtype))
    out_height = simplify(
        (height -
         kernel_height +
         pad_top +
         pad_down) //
        stride_height +
        1)
    out_width = simplify(
        (width -
         kernel_width +
         pad_left +
         pad_right) //
        stride_width +
        1)
    dheight = hcl.reduce_axis(0, kernel_height)
    dwidth = hcl.reduce_axis(0, kernel_width)

    return hcl.compute(
        (batch, channel, out_height, out_width),
        lambda i, c, h, w: max(data[i, c, h *
                                    stride_height +
                                    dheight, w *
                                    stride_width +
                                    dwidth], axis=[dheight, dwidth]),
        name=name,
        attrs=OrderedDict([
            ('out_img_w', out_width),
            ('out_img_h', out_height),
            ('in_num', channel),
            ('kernel_h', kernel[1]),
            ('kernel_w', kernel[0]),
            ('stride_h', stride[1]),
            ('stride_w', stride[0]),
            ('app_name', tvm.make.StringImm('max_pool'))]))

def max_pool2d(
        data,
        pool_size=[1, 1],
        strides=[1, 1],
        padding=[0, 0],
        layout='NCHW',
        name='max_pool2d'):
    pooling = []
    stride = []
    pad = []
    for i in range(len(pool_size)):
        pooling.append(tvm_to_primitive(pool_size[i]))
        stride.append(tvm_to_primitive(strides[i]))
    for i in range(len(padding)):
        pad.append(tvm_to_primitive(padding[i]))
    if layout == 'NCHW':
        out = max_pool2d_nchw(data, pooling, stride, pad, name)
    elif layout == 'NHWC':
        out = max_pool2d_nhwc(data, pooling, stride, pad, name)
    else:
        raise ValueError("not support this layout {} yet".format(layout))
    return out

def max_pool2d_nchw(data, pooling, stride, padding, name='max_pool2d'):
    assert len(data.shape) == 4, "only support 4-dim pooling"
    assert len(stride) == 2, "only support 2-dim stride"
    max = hcl.reducer(
        tvm.min_value(data.dtype),
        lambda x, y: tvm.make.Max(x, y),
        data.dtype)
    pooling_h, pooling_w = pooling
    stride_h, stride_w = stride
    batch, channel, height, width = data.shape
    if len(padding) == 4:
        pad_top = padding[0]
        pad_left = padding[1]
        pad_bottom = padding[2]
        pad_right = padding[3]
    else:
        pad_top, pad_left, pad_bottom, pad_right = get_pad_tuple(padding, (pooling_h, pooling_w))
    pad_before = [0, 0, pad_top, pad_left]
    pad_after = [0, 0, pad_bottom, pad_right]
    data = pad(data, pad_before, pad_after, pad_value=tvm.min_value(data.dtype))
    out_height = simplify(
        (height - pooling_h + pad_top + pad_bottom) // stride_h + 1)
    out_width = simplify(
        (width - pooling_w + pad_left + pad_right) // stride_w + 1)
    dheight = hcl.reduce_axis(0, pooling_h)
    dwidth = hcl.reduce_axis(0, pooling_w)
    return hcl.compute(
        (batch, channel, out_height, out_width),
        lambda i, c, h, w: max(data[i, c, h *
                                    stride_h +
                                    dheight, w *
                                    stride_w +
                                    dwidth], axis=[dheight, dwidth]),
        name=name, dtype=data.dtype,
        attrs=OrderedDict([
            ('out_img_w', out_width),
            ('out_img_h', out_height),
            ('in_num', channel),
            ('kernel_h', pooling[1]),
            ('kernel_w', pooling[0]),
            ('stride_h', stride[1]),
            ('stride_w', stride[0]),
            ('app_name', tvm.make.StringImm('max_pool'))]))

def max_pool2d_nhwc(
        data,
        pooling,
        stride=[1, 1],
        padding=[0, 0],
        name='max_pool2d'):
    assert len(data.shape) == 4, "only support 4-dim pooling"
    assert len(stride) == 2, "only support 2-dim stride"
    max = hcl.reducer(
        tvm.min_value(
            data.dtype), lambda x, y: tvm.make.Max(
            x, y), data.dtype)
    pooling_h, pooling_w = pooling
    stride_h, stride_w = stride
    batch, height, width, channel = data.shape
    if len(padding) == 4:
        pad_top = padding[0]
        pad_left = padding[1]
        pad_bottom = padding[2]
        pad_right = padding[3]
    else:
        pad_top, pad_left, pad_bottom, pad_right = get_pad_tuple(padding, (pooling_h, pooling_w))
    pad_before = [0, pad_top, pad_left, 0]
    pad_after = [0, pad_bottom, pad_right, 0]
    data = pad(
        data,
        pad_before,
        pad_after,
        pad_value=tvm.min_value(
            data.dtype))
    out_height = simplify(
        (height - pooling_h + pad_top + pad_bottom) // stride_h + 1)
    out_width = simplify(
        (width - pooling_w + pad_left + pad_right) // stride_w + 1)
    dheight = hcl.reduce_axis(0, pooling_h)
    dwidth = hcl.reduce_axis(0, pooling_w)
    return hcl.compute(
        (batch, out_height, out_width, channel),
        lambda i, h, w, c: max(data[i, h *
                                    stride_h +
                                    dheight, w *
                                    stride_w +
                                    dwidth, c], axis=[dheight, dwidth]),
        name=name,
        attrs=OrderedDict([
            ('out_img_w', out_width),
            ('out_img_h', out_height),
            ('in_num', channel),
            ('kernel_h', pooling[1]),
            ('kernel_w', pooling[0]),
            ('stride_h', stride[1]),
            ('stride_w', stride[0]),
            ('app_name', tvm.make.StringImm('max_pool'))]))

def avg_pool2d(
        data,
        pool_size=[1, 1],
        strides=[1, 1],
        padding=[0, 0],
        layout='NCHW',
        name='avg_pool2d'):
    pooling = []
    stride = []
    pad = []
    for i in range(len(pool_size)):
        pooling.append(tvm_to_primitive(pool_size[i]))
        stride.append(tvm_to_primitive(strides[i]))
        pad.append(tvm_to_primitive(padding[i]))
    if layout == 'NCHW':
        out = avg_pool2d_nchw(data, pooling, stride, pad, name)
    elif layout == 'NHWC':
        out = avg_pool2d_nhwc(data, pooling, stride, pad, name)
    else:
        raise ValueError("not support this layout {} yet".format(layout))
    return out

def avg_pool2d_nchw(data, pooling, stride, padding, name='avg_pool2d'):
    assert len(data.shape) == 4, "only support 4-dim pooling"
    assert len(stride) == 2, "only support 2-dim stride"
    pooling_h, pooling_w = pooling
    stride_h, stride_w = stride
    batch, channel, height, width = data.shape
    if len(padding) == 4:
        pad_top, pad_left, pad_bottom, pad_right = padding
    else:
        pad_top, pad_left, pad_bottom, pad_right = get_pad_tuple(
            padding, (pooling_h, pooling_w))
    pad_before = [0, 0, pad_top, pad_left]
    pad_after = [0, 0, pad_bottom, pad_right]
    data = pad(
        data,
        pad_before,
        pad_after,
        pad_value=tvm.const(
            0.0,
            data.dtype))
    out_height = simplify(
        (height - pooling_h + pad_top + pad_bottom) // stride_h + 1)
    out_width = simplify(
        (width - pooling_w + pad_left + pad_right) // stride_w + 1)
    dheight = hcl.reduce_axis(0, pooling_h)
    dwidth = hcl.reduce_axis(0, pooling_w)
    return hcl.compute(
        (batch, channel, out_height, out_width),
        lambda i, c, h, w: (sum(data[i, c, h *
                                     stride_h +
                                     dheight, w *
                                     stride_w +
                                     dwidth], axis=[dheight, dwidth]) /
                            (pooling_w *
                             pooling_h)),
        name=name,
        attrs=OrderedDict([
            ('out_img_w', out_width),
            ('out_img_h', out_height),
            ('in_num', channel),
            ('kernel_h', pooling[1]),
            ('kernel_w', pooling[0]),
            ('stride_h', stride[1]),
            ('stride_w', stride[0]),
            ('app_name', tvm.make.StringImm('avg_pool'))]))

def avg_pool2d_nhwc(
    data, pooling, stride=[
        1, 1], padding=[
            0, 0], name='avg_pool2d'):
    assert len(data.shape) == 4, "only support 4-dim pooling"
    assert len(stride) == 2, "only support 2-dim stride"
    pooling_h, pooling_w = pooling
    stride_h, stride_w = stride
    batch, height, width, channel = data.shape
    pad_top, pad_left, pad_bottom, pad_right = get_pad_tuple(
        padding, (pooling_h, pooling_w))
    pad_before = [0, pad_top, pad_left, 0]
    pad_after = [0, pad_bottom, pad_right, 0]
    data = pad(
        data,
        pad_before,
        pad_after,
        pad_value=tvm.const(
            0.0,
            data.dtype))
    out_height = simplify(
        (height - pooling_h + pad_top + pad_bottom) // stride_h + 1)
    out_width = simplify(
        (width - pooling_w + pad_left + pad_right) // stride_w + 1)
    dheight = hcl.reduce_axis(0, pooling_h)
    dwidth = hcl.reduce_axis(0, pooling_w)
    return hcl.compute(
        (batch, out_height, out_width, channel),
        lambda i, h, w, c: sum(data[i, h * stride_h + dheight, w * stride_w +
            dwidth, c], axis=[dheight, dwidth]) / (pooling_w * pooling_h),
            name=name,
            attrs=OrderedDict([
                ('out_img_w', out_width),
                ('out_img_h', out_height),
                ('in_num', channel),
                ('kernel_h', pooling[1]),
                ('kernel_w', pooling[0]),
                ('stride_h', stride[1]),
                ('stride_w', stride[0]),
                ('app_name', tvm.make.StringImm('avg_pool'))]))

def global_max_pool2d(data, layout='NCHW', name='global_max_pool2d'):
    stride = [1, 1]
    padding = [0, 0]
    if layout == 'NCHW':
        pooling = [data.shape[2], data.shape[3]]
        return max_pool2d_nchw(data, pooling, stride, padding, name)
    if layout == 'NHWC':
        pooling = [data.shape[1], data.shape[2]]
        return max_pool2d_nhwc(data, pooling, stride, padding, name)
    raise ValueError("not support this layout {} yet".format(layout))

def global_avg_pool2d(data, layout='NCHW', name='global_avg_pool2d'):
    stride = [1, 1]
    padding = [0, 0]
    if layout == 'NCHW':
        pooling = [data.shape[2], data.shape[3]]
        return avg_pool2d_nchw(data, pooling, stride, padding, name)
    if layout == 'NHWC':
        pooling = [data.shape[1], data.shape[2]]
        return avg_pool2d_nhwc(data, pooling, stride, padding, name)
    raise ValueError("not support this layout {} yet".format(layout))

def transpose(data, axes=[], name="transpose"):
    new_shape = []
    if len(axes) == 0:
        for i in range(len(data.shape)):
            axes.append(i)
    for i in range(len(axes)):
        new_axis = tvm_to_primitive(axes[i])
        if tvm_to_primitive(axes[i]) < 0:
            new_axis = len(data.shape) + tvm_to_primitive(axes[i])
            axes[i] = new_axis
        assert (
            new_axis >= 0 and new_axis < len(
                data.shape)), "axis={} is invalid for the {}-dimensional input tensor".format(
            new_axis, len(
                data.shape))
        for j in range(len(axes)):
            if (not i == j):
                assert(not new_axis == axes[j]), "repeated axis in transpose"
        new_shape.append(data.shape[new_axis])
    new_shape = tuple(new_shape)

    def _transpose(*indices):
        if len(axes) != 0:
            idx = [1] * len(axes)
            for i in range(len(axes)):
                idx[tvm_to_primitive(axes[i])] = indices[0][i]
        else:
            idx = indices[0]
        return idx
    return hcl.compute(new_shape, lambda *x: data[tuple(_transpose(x))], name=name,
        attrs=OrderedDict([('app_name',tvm.make.StringImm('transpose'))]))

def flatten(data, name="flatten"):
    ishape = data.shape
    dim = 1
    for i in range(1, len(ishape)):
        dim = dim * ishape[i]
    oshape = (ishape[0], dim)

    def unwrap(idx, shape):
        index = []
        for s in reversed(shape):
            index.append(idx % s)
            idx = idx / s
        return list(reversed(index))

    return hcl.compute(oshape, lambda i,j: data[tuple([i] + unwrap(j,ishape[1:]))],
        name=name,attrs=OrderedDict([('app_name',tvm.make.StringImm('flatten'))]))

def softmax(x, name="softmax", axis=0, frontend='keras'):
    shape = x.shape
    k = hcl.reduce_axis(0, shape[axis])
    new_shape = []
    for i in range(len(shape)):
        if i != axis:
            new_shape.append(shape[i])

    def _reduce_axis(axis, new_axis, keep_axis, *indices):
        indices = indices[0]
        new_ind = []
        put_axis = False
        for i in range(len(indices)):
            if i == axis and keep_axis:
                new_ind.append(new_axis)
                put_axis = True
                new_ind.append(indices[i])
            elif i != axis:
                new_ind.append(indices[i])
        if put_axis == False and keep_axis:
            new_ind.append(new_axis)
        return tuple(new_ind)
    max_elem = hcl.compute(
        tuple(new_shape), lambda *y: max(x[_reduce_axis(axis, k, True, y)], axis=[k]))
    k = hcl.reduce_axis(0, shape[axis])
    expsum = hcl.compute(
        tuple(new_shape), lambda *y: sum(tvm.exp(x[_reduce_axis(axis, k, True, y)] - max_elem[y]), axis=k))
    return hcl.compute(
        x.shape, lambda *y: tvm.exp(x[y] - max_elem[_reduce_axis(axis, k, False, y)]) / expsum[_reduce_axis(axis, k, False, y)], name)

def relu(data, name='relu'):
    return hcl.compute(data.shape,lambda *y: hcl.select(
        data[y] < 0, hcl.cast(data.dtype, 0), data[y]), name)

def leakyrelu(data, alpha=0.01):
    return hcl.compute(
        data.shape, lambda *y: hcl.select(data[y] < 0, alpha * data[y], data[y]))

def prelu(data, alpha, axis=1):
    def _axis_ind(axis, ind):
        ind = ind[0]
        new_ind = []
        for i in range(len(ind)):
            if i == axis:
                new_ind = ind[i]
        return tuple(new_ind)
    return hcl.compute(data.shape,lambda *x: hcl.select(
        data[x] < 0,
        hcl.cast(data.dtype, alpha[_axis_ind(axis,x)] * data[x]),
        data[x]))

def elu(data, alpha):
    return hcl.compute(data.shape,lambda *x: hcl.select(
        data[x] < 0, alpha * (hcl.exp(data[x])-1), data[x]))

def thresholdedrelu(data, theta):
    return hcl.compute(data.shape,lambda *x: hcl.select(
        data[x] > theta, data[x], hcl.cast(data.dtype, 0)))