import os
from pathlib import Path
from typing import Callable, List

import numpy as np
import pytest

from gaddlemaps import rotation_matrix
from gaddlemaps.components import (Atom, AtomGro, AtomTop, Molecule,
                                   MoleculeTop, Residue, System)
from gaddlemaps.components._components import _molecule_top_and_residues_match
from gaddlemaps.parsers import GroFile

AtomGenerator = Callable[..., AtomGro]

ACTUAL_PATH = os.path.split(os.path.join(os.path.abspath(__file__)))[0]

@pytest.fixture
def atom_gro_generator() -> AtomGenerator:
    """
    A function to create AtomGro instances.
    """
    def generate_atom_gro(resid: int = 1, resname: str = 'BMIM',
                          name: str = 'N1', atomid: int = 1) -> AtomGro:
        info = (resid, resname, name, atomid, 4.668,
                3.571, 8.232, -0.2489, 0.2514, 0.1046)
        return AtomGro(info)
    return generate_atom_gro

@pytest.fixture
def atom_n1_bmim(atom_gro_generator: AtomGenerator) -> Atom:
    """
    N1 Atom instance of BMIM.
    """
    atom_gro = atom_gro_generator()
    atom_top = AtomTop('N1', 'BMIM', 1, 0)
    return Atom(atom_top, atom_gro)

@pytest.fixture
def molecule_top_bmim() -> MoleculeTop:
    """
    BMIM MoleculeTop instance.
    """
    fname = os.path.join(ACTUAL_PATH, '../../gaddlemaps/data/BMIM_AA.itp')
    return MoleculeTop(fname)

@pytest.fixture
def residue_bmim() -> Residue:
    """
    BMIM Residue instance.
    """
    fname = os.path.join(ACTUAL_PATH, '../../gaddlemaps/data/BMIM_AA.gro')
    with GroFile(fname) as fgro:
        atoms = [AtomGro(line) for line in fgro]
    return Residue(atoms)

@pytest.fixture
def molecule_bmim(molecule_top_bmim: MoleculeTop,
                  residue_bmim: Residue) -> Molecule:
    """
    BMIM Molecule instance.
    """
    return Molecule(molecule_top_bmim, [residue_bmim])

@pytest.fixture
def molecule_top_protein() -> MoleculeTop:
    """
    MoleculeTop instance for molecule with multiple residues.
    """
    fname = os.path.join(ACTUAL_PATH, '../../gaddlemaps/data/Protein_CG.itp')
    return MoleculeTop(fname)

@pytest.fixture
def residue_protein() -> List[Residue]:
    """
    List of Residue instances for a molecule with multiple residues.
    """
    fname = os.path.join(ACTUAL_PATH, '../../gaddlemaps/data/Protein_CG.gro')
    residues: List[Residue] = []
    with GroFile(fname) as fgro:
        atom = AtomGro(next(fgro))
        residname = atom.residname
        atoms = [atom]
        for line in fgro:
            atom = AtomGro(line)
            if residname != atom.residname:
                residues.append(Residue(atoms))
                residname = atom.residname
                atoms = [atom]
            else:
                atoms.append(atom)
    residues.append(Residue(atoms))
    return residues

@pytest.fixture
def molecule_protein(molecule_top_protein: MoleculeTop,
                     residue_protein: List[Residue]) -> Molecule:
    """
    Molecule instance with multiple residues.
    """
    return Molecule(molecule_top_protein, residue_protein)

def test_molecule_top_and_residues_match(molecule_top_bmim: MoleculeTop,
                                         residue_bmim: Residue,
                                         molecule_top_protein: MoleculeTop,
                                         residue_protein: List[Residue]):
    """
    Test the match between residues and MoleculeTop.
    """
    # different length
    assert not _molecule_top_and_residues_match(molecule_top_bmim,
                                                residue_protein)

    assert _molecule_top_and_residues_match(molecule_top_bmim,
                                            [residue_bmim])
    assert _molecule_top_and_residues_match(molecule_top_protein,
                                            residue_protein)
    residue_bmim.resname = 'test'
    assert not _molecule_top_and_residues_match(molecule_top_bmim,
                                                [residue_bmim])

class TestAtom:
    """
    Wraps all the tests for the Atom class.
    """
    def test_initialization(self, atom_gro_generator: AtomGenerator):
        """
        Tests Atom initialization and own attributes
        """
        atom_gro = atom_gro_generator()
        atom_top = AtomTop('N1', 'BMIM', 1, 0)
        atom = Atom(atom_top, atom_gro)
        assert atom.atom_gro is atom_gro
        assert atom.atom_top is atom_top
        new_atom = atom.copy()
        assert new_atom == atom
        assert atom != 0
        assert new_atom is not atom
        assert hash(atom) == 0

        with pytest.raises(TypeError, match="atom_gro.*"):
            atom = Atom(atom_top, atom_top)  # type:ignore
        with pytest.raises(TypeError, match="atom_top.*"):
            atom = Atom(atom_gro, atom_gro)  # type:ignore

        atom_gro.name = 'B5'
        with pytest.raises(IOError):
            atom = Atom(atom_top, atom_gro)

        atom_gro.name = 'N1'
        atom_gro.resname = 'BF4'
        with pytest.raises(IOError):
            atom = Atom(atom_top, atom_gro)

        # Print and str
        atom_str = str(atom)
        assert atom_str == repr(atom)
        line = 'Atom N1 of molecule BF4 with gro residue number 1.'
        assert atom_str == line

        # Some check to the dir()
        atom_dir = dir(atom)
        for key in dir(atom.atom_gro):
            assert key in atom_dir
        for key in dir(atom.atom_top):
            assert key in atom_dir

    def test_get_set_attributes(self, atom_n1_bmim: Atom):
        """
        Tests get and set attributes for Atom.
        """
        # Name
        assert atom_n1_bmim.name == 'N1'
        atom_n1_bmim.name = 'B5'
        assert atom_n1_bmim.name == 'B5'
        assert atom_n1_bmim._atom_gro.name == 'B5'
        assert atom_n1_bmim._atom_top.name == 'B5'

        # Resname
        assert atom_n1_bmim.resname == 'BMIM'
        atom_n1_bmim.resname = 'BF4'
        assert atom_n1_bmim.resname == 'BF4'
        assert atom_n1_bmim._atom_gro.resname == 'BF4'
        assert atom_n1_bmim._atom_top.resname == 'BF4'

        # resids
        with pytest.raises(AttributeError):
            resid = atom_n1_bmim.resid
        with pytest.raises(AttributeError):
            atom_n1_bmim.resid = 1

        # Other access and set
        with pytest.raises(AttributeError):
            resid = atom_n1_bmim.test
        atom_n1_bmim.test = 1
        assert atom_n1_bmim.test == 1

        assert atom_n1_bmim.gro_resid == 1
        atom_n1_bmim.gro_resid = 3
        assert atom_n1_bmim.gro_resid == 3
        assert atom_n1_bmim._atom_gro.resid == 3

        assert atom_n1_bmim.top_resid == 1
        atom_n1_bmim.top_resid = 5
        assert atom_n1_bmim.top_resid == 5
        assert atom_n1_bmim._atom_top.resid == 5

        # Residname is the gro one
        assert atom_n1_bmim.residname == '3BF4'

        # index
        assert atom_n1_bmim.index == 0
        assert hash(atom_n1_bmim) == 0
        atom_n1_bmim.index = 5
        assert atom_n1_bmim.index == 5
        assert hash(atom_n1_bmim) == 5
        assert atom_n1_bmim._atom_top.index == 5

        # bonds
        assert atom_n1_bmim.bonds == atom_n1_bmim._atom_top.bonds
        atom_n1_bmim.bonds = {1, 32, 4}
        assert atom_n1_bmim.bonds == {1, 32, 4}
        assert atom_n1_bmim.closest_atoms() == [1, 4]
        atom_n1_bmim.connect(atom_n1_bmim)
        assert atom_n1_bmim.bonds == {1, 32, 4, 5}

        # atomid
        assert atom_n1_bmim.atomid == 1
        atom_n1_bmim.atomid = 6
        assert atom_n1_bmim.atomid == 6
        assert atom_n1_bmim._atom_gro.atomid == 6

        # element
        assert atom_n1_bmim.element == 'B'

        # position
        assert np.isclose(atom_n1_bmim.position, [4.668, 3.571, 8.232]).all()
        atom_n1_bmim.position = np.array([0, 0, 0])
        assert np.isclose(atom_n1_bmim.position, [0, 0, 0]).all()
        assert np.isclose(atom_n1_bmim._atom_gro.position, [0, 0, 0]).all()

        # velocity
        assert np.isclose(atom_n1_bmim.velocity, [-0.2489, 0.2514, 0.1046]).all()
        atom_n1_bmim.velocity = None
        assert atom_n1_bmim.velocity is None
        assert atom_n1_bmim._atom_gro.velocity is None

        # gro_line
        assert atom_n1_bmim.gro_line() == atom_n1_bmim._atom_gro.gro_line()

class TestMolecule:
    """
    Wraps all the tests for the Molecule class.
    """
    def test_init_one_residue(self, molecule_top_bmim: MoleculeTop,
                              residue_bmim: Residue):
        """
        Tests Molecule initialization with only one residue and some basic
        attributes and methods.
        """
        # Wrong residue
        new_residue = Residue(residue_bmim[:-1])
        with pytest.raises(IOError):
            molec = Molecule(molecule_top_bmim, [new_residue])

        molec = Molecule(molecule_top_bmim, [residue_bmim])

        assert len(molec) == len(molecule_top_bmim)
        assert len(molec) == len(residue_bmim)
        assert len(molec) == 25

        assert str(molec) == 'Molecule of BMIM.'

        for atom, atom_top, atom_gro in zip(molec, molecule_top_bmim,
                                            residue_bmim):
            atom_compare = Atom(atom_top, atom_gro)
            assert atom == atom_compare

        for atom, atom_comp in zip(molec, molec.atoms):
            assert atom == atom_comp

        assert molec[-1] == atom_compare
        assert atom_compare != molec[0]
        assert molec.index(atom_compare) == 24
        with pytest.raises(ValueError):
            _ = molec.index(34)  # type: ignore

        assert molec._each_atom_resid == [0] * len(molec)

        assert molec == molec
        assert molec is molec

        copy_mol = molec.copy()
        assert copy_mol == molec
        assert copy_mol is not molec

        assert len(molec.residues) == 1
        assert molec.residues[0] == residue_bmim
        assert molec.residues[0] is not residue_bmim
        assert molec.molecule_top is molecule_top_bmim

        with pytest.raises(NotImplementedError):
            _ = molec + 1  # type: ignore
        with pytest.raises(NotImplementedError):
            _ = 1 + molec  # type: ignore

        for key in dir(molec.molecule_top):
            assert key in dir(molec)

    def test_init_multi_residue(self, molecule_top_protein: MoleculeTop,
                                residue_protein: List[Residue]):
        """
        Tests Molecule initialization with multiple residues and some basic
        attributes and methods.
        """
        molec = Molecule(molecule_top_protein, residue_protein)

        assert len(molec) == len(molecule_top_protein)
        assert len(molec) == sum(len(res) for res in residue_protein)
        assert len(molec) == 34

        index = 0
        for res in residue_protein:
            for atom_gro in res:
                atom_compare = Atom(molecule_top_protein[index], atom_gro)
                assert molec[index] == atom_compare
                index += 1

        for atom, atom_comp in zip(molec, molec.atoms):
            assert atom == atom_comp

        assert molec[-1] == atom_compare
        assert molec.index(atom_compare) == 33

        assert molec._each_atom_resid == [0, 0, 0, 1, 1, 1, 2, 2, 3,
                                          3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 7, 7,
                                          8, 8, 8, 9, 9, 10, 10, 10, 11, 11,
                                          11, 12, 12]

        assert molec != 0
        assert molec == molec
        assert molec is molec

        assert molec.copy() == molec
        assert molec.copy() is not molec

        assert len(molec.residues) == 13
        for res1, res2 in zip(molec.residues, residue_protein):
            assert res1 == res2
            assert res1 is not res2
        assert molec.molecule_top is molecule_top_protein

    def test_init_from_files(self):
        """
        Test the molecule initialization from files.
        """
        fitp = os.path.join(ACTUAL_PATH, '../../gaddlemaps/data/BMIM_AA.itp')
        fgro = os.path.join(ACTUAL_PATH, '../../gaddlemaps/data/BMIM_AA.gro')
        bmim = Molecule.from_files(fgro, fitp)
        assert len(bmim) == 25
        fitp = os.path.join(ACTUAL_PATH, '../../gaddlemaps/data/BMIM_CG.itp')
        fgro = os.path.join(ACTUAL_PATH, '../../gaddlemaps/data/system_bmimbf4_cg.gro')
        with pytest.raises(IOError):
            bmim = Molecule.from_files(fgro, fitp)

    def test_resnames(self, molecule_protein: Molecule,
                      molecule_bmim: Molecule):
        """
        Tests for the resnames property
        """
        assert molecule_bmim.resnames == ['BMIM']
        molecule_bmim.resnames = ['BF4']
        assert molecule_bmim.resnames == ['BF4']
        assert molecule_bmim.molecule_top.resnames == ['BF4']
        assert molecule_bmim.residues[0].resname == 'BF4'
        for atom in molecule_bmim:
            assert atom.resname == 'BF4'

        with pytest.raises(ValueError):
            molecule_bmim.resnames = ['BF4', 'Test']
        with pytest.raises(TypeError):
            molecule_bmim.resnames = 4  # type: ignore

        molecule_bmim.resnames = 'test'  # type: ignore
        assert molecule_bmim.resnames == ['test']
        assert molecule_bmim.molecule_top.resnames == ['test']
        assert molecule_bmim.residues[0].resname == 'test'
        for atom in molecule_bmim:
            assert atom.resname == 'test'

        resnames_compare = ['ARG', 'ARG', 'LEU', 'LYS', 'ARG', 'LEU', 'LEU',
                            'ARG', 'ARG', 'LEU', 'LYS', 'ARG', 'LEU']
        assert molecule_protein.resnames == resnames_compare
        resnames_compare[3] = 'Test'
        molecule_protein.resnames = resnames_compare
        assert molecule_protein.resnames == resnames_compare
        for res, res_test in zip(molecule_protein.residues, resnames_compare):
            assert res.resname == res_test
        for index, atom in enumerate(molecule_protein):
            res_index = molecule_protein._each_atom_resid[index]
            assert atom.resname == resnames_compare[res_index]

        molecule_protein.resnames = 'hello'  # type: ignore
        assert molecule_protein.resnames == ['hello'] * len(resnames_compare)
        for res, res_test in zip(molecule_protein.residues, resnames_compare):
            assert res.resname == 'hello'
        for atom in molecule_protein:
            assert atom.resname == 'hello'

    def test_resids(self, molecule_protein: Molecule,
                    molecule_bmim: Molecule):
        """
        Tests for the resids property
        """
        assert molecule_bmim.resids == [1]
        molecule_bmim.resids = [5]
        assert molecule_bmim.resids == [5]
        assert molecule_bmim.molecule_top.resids == [5]
        assert molecule_bmim.residues[0].resid == 5
        for atom in molecule_bmim:
            assert atom.top_resid == 5
            assert atom.gro_resid == 5

        with pytest.raises(ValueError):
            molecule_bmim.resids = [1, 2]
        with pytest.raises(TypeError):
            molecule_bmim.resids = 'test'  # type: ignore

        molecule_bmim.resids = 3  # type: ignore
        assert molecule_bmim.resids == [3]
        assert molecule_bmim.molecule_top.resids == [3]
        assert molecule_bmim.residues[0].resid == 3
        for atom in molecule_bmim:
            assert atom.top_resid == 3
            assert atom.gro_resid == 3

        resids_compare = list(range(1, 14))
        assert molecule_protein.resids == resids_compare
        resids_compare[3] = 8
        molecule_protein.resids = resids_compare
        assert molecule_protein.resids == resids_compare
        for res, res_test in zip(molecule_protein.residues, resids_compare):
            assert res.resid == res_test
        for index, atom in enumerate(molecule_protein):
            res_index = molecule_protein._each_atom_resid[index]
            assert atom.top_resid == resids_compare[res_index]
            assert atom.gro_resid == resids_compare[res_index]

        molecule_protein.resids = 49  # type: ignore
        assert molecule_protein.resids == [49] * len(resids_compare)
        for res, res_test in zip(molecule_protein.residues, resids_compare):
            assert res.resid == 49
        for atom in molecule_protein:
            assert atom.top_resid == 49
            assert atom.gro_resid == 49

    def test_get_set_attributes(self, molecule_top_bmim: MoleculeTop,
                                molecule_bmim: Molecule):
        """
        Tests get and set attributes for Molecule. Molecule getattr just access
        the molecule_top attributes.
        """
        # Name
        assert molecule_top_bmim.name == molecule_bmim.name
        molecule_bmim.name = 'Test'
        assert molecule_bmim.name == 'Test'
        assert molecule_bmim.molecule_top.name == 'Test'

        # Resname len list
        assert molecule_bmim.resname_len_list == [('BMIM', 25)]

    def test_bonds_distance(self, molecule_bmim: Molecule):
        """
        Test bonds distances for bmim.
        """
        bonds_dist = molecule_bmim.bonds_distance
        for index, atom in enumerate(molecule_bmim):
            assert set(val[0] for val in bonds_dist[index]) == atom.bonds
            for index_to, dist in bonds_dist[index]:
                diff = atom.position - molecule_bmim[index_to].position
                dist_test = np.linalg.norm(diff)
                assert round(dist, 5) == round(dist_test, 5)

    def test_position_velocities_bmim(self, molecule_bmim: Molecule):
        """
        Test for the atom position and velocities get and setter for bmim
        molecule.
        """
        test_bmim_pos = np.array([
            [1.593, 1.896, 0.729],
            [1.706, 1.984, 0.708],
            [1.807, 1.892, 0.660],
            [1.755, 1.761, 0.652],
            [1.626, 1.764, 0.693],
            [1.738, 2.029, 0.805],
            [1.809, 1.674, 0.618],
            [1.559, 1.679, 0.698],
            [1.464, 1.937, 0.778],
            [1.942, 1.930, 0.624],
            [2.044, 1.858, 0.713],
            [2.188, 1.899, 0.677],
            [2.289, 1.828, 0.765],
            [1.463, 2.046, 0.799],
            [1.441, 1.883, 0.872],
            [1.385, 1.914, 0.703],
            [1.960, 1.904, 0.517],
            [1.955, 2.039, 0.636],
            [2.024, 1.884, 0.819],
            [2.034, 1.748, 0.701],
            [2.200, 2.009, 0.689],
            [2.208, 1.872, 0.571],
            [2.392, 1.859, 0.737],
            [2.273, 1.854, 0.872],
            [2.281, 1.718, 0.753]
        ])
        assert np.isclose(test_bmim_pos, molecule_bmim.atoms_positions).all()
        assert molecule_bmim.atoms_velocities is None

        test_bmim_pos[1] = np.array((1, 1, 1))
        molecule_bmim.atoms_positions = test_bmim_pos
        assert np.isclose(test_bmim_pos, molecule_bmim.atoms_positions).all()

        change_atom = np.array((2, 3, 4))
        test_bmim_pos[3] = change_atom
        molecule_bmim[3].position = change_atom
        assert np.isclose(molecule_bmim[3].position, change_atom).all()
        assert np.isclose(test_bmim_pos, molecule_bmim.atoms_positions).all()

        geom_test = np.mean(test_bmim_pos, axis=0)
        assert np.isclose(geom_test, molecule_bmim.geometric_center).all()

        with pytest.raises(ValueError):
            molecule_bmim.atoms_positions = change_atom

        with pytest.raises(ValueError):
            molecule_bmim.atoms_velocities = change_atom

        molecule_bmim.atoms_velocities = test_bmim_pos
        assert np.isclose(test_bmim_pos, molecule_bmim.atoms_velocities).all()

        change_atom = np.array((5, 1, 3))
        test_bmim_pos[5] = change_atom
        assert np.isclose(molecule_bmim[5].velocity, change_atom).all()
        assert np.isclose(test_bmim_pos, molecule_bmim.atoms_velocities).all()

    def test_position_velocities_protein(self, molecule_protein: Molecule):
        """
        Test for the atom position and velocities get and setter for the
        molecule with multiple residues.
        """
        test_protein_pos = np.array([
            [2.490, 3.850, 5.330],
            [2.310, 4.040, 5.170],
            [2.190, 4.380, 5.220],
            [2.480, 3.530, 5.200],
            [2.280, 3.350, 5.350],
            [2.180, 3.040, 5.290],
            [2.700, 3.410, 5.120],
            [2.770, 3.370, 4.800],
            [2.900, 3.290, 5.390],
            [3.220, 3.190, 5.390],
            [3.440, 3.130, 5.540],
            [2.700, 3.030, 5.470],
            [2.490, 3.060, 5.730],
            [2.430, 3.160, 6.050],
            [2.710, 2.860, 5.200],
            [2.550, 2.950, 4.940],
            [3.040, 2.820, 5.150],
            [3.110, 2.980, 4.860],
            [3.090, 2.690, 5.460],
            [3.280, 2.580, 5.720],
            [3.120, 2.490, 5.990],
            [2.830, 2.450, 5.450],
            [2.750, 2.270, 5.670],
            [2.460, 2.210, 5.800],
            [2.910, 2.280, 5.170],
            [2.850, 2.430, 4.850],
            [3.210, 2.310, 5.160],
            [3.250, 2.010, 5.010],
            [3.330, 1.820, 5.160],
            [3.220, 2.140, 5.500],
            [3.550, 2.070, 5.510],
            [3.830, 2.170, 5.540],
            [3.050, 1.870, 5.590],
            [2.790, 1.770, 5.390]
        ])
        assert np.isclose(test_protein_pos, molecule_protein.atoms_positions).all()
        assert molecule_protein.atoms_velocities is None

        test_protein_pos[1] = np.array((1, 1, 1))
        molecule_protein.atoms_positions = test_protein_pos
        assert np.isclose(test_protein_pos, molecule_protein.atoms_positions).all()

        change_atom = np.array((2, 3, 4))
        test_protein_pos[3] = change_atom
        molecule_protein[3].position = change_atom
        assert np.isclose(molecule_protein[3].position, change_atom).all()
        assert np.isclose(test_protein_pos, molecule_protein.atoms_positions).all()

        geom_test = np.mean(test_protein_pos, axis=0)
        assert np.isclose(geom_test, molecule_protein.geometric_center).all()

        with pytest.raises(ValueError):
            molecule_protein.atoms_positions = change_atom

        with pytest.raises(ValueError):
            molecule_protein.atoms_velocities = change_atom

        molecule_protein.atoms_velocities = test_protein_pos
        assert np.isclose(test_protein_pos, molecule_protein.atoms_velocities).all()

        change_atom = np.array((5, 1, 3))
        test_protein_pos[5] = change_atom
        assert np.isclose(molecule_protein[5].velocity, change_atom).all()
        assert np.isclose(test_protein_pos, molecule_protein.atoms_velocities).all()

    def test_atoms_ids(self, molecule_protein: Molecule):
        """
        Test for the atoms_ids property for the molecule with multiple residues.
        """
        test_ids = list(range(1, 35))
        assert molecule_protein.atoms_ids == test_ids

        test_ids[2] = 5
        molecule_protein.atoms_ids = test_ids
        assert molecule_protein.atoms_ids == test_ids

        test_ids[4] = 40
        molecule_protein[4].atomid = 40
        assert molecule_protein.atoms_ids == test_ids

        with pytest.raises(IndexError):
            molecule_protein.atoms_ids = [1, 2]

        with pytest.raises(TypeError):
            test_wrong = [1] * (len(test_ids) - 1) + ['test']  # type: ignore
            molecule_protein.atoms_ids = test_wrong

    def test_hidden_methods(self, molecule_bmim: Molecule):
        """
        Test not implemented properties and methods.
        """
        with pytest.raises(AttributeError):
            test = molecule_bmim.resname
        with pytest.raises(AttributeError):
            molecule_bmim.resname = 'test'

        with pytest.raises(AttributeError):
            test = molecule_bmim.resid # type: ignore
        with pytest.raises(AttributeError):
            molecule_bmim.resid = 'test'  # type: ignore

        with pytest.raises(AttributeError):
            test = molecule_bmim.residname
        with pytest.raises(AttributeError):
            molecule_bmim.residname = 'test'  # type: ignore

        with pytest.raises(AttributeError):
            test = molecule_bmim.remove_atom(molecule_bmim[0]) # type: ignore

    def test_rotate(self, molecule_protein: Molecule):
        """
        Test for the rotate method.
        """
        rot_mat = rotation_matrix([0, 0, 1], 2*np.pi)
        old_pos = molecule_protein.atoms_positions
        molecule_protein.rotate(rot_mat)
        assert np.isclose(old_pos, molecule_protein.atoms_positions).all()

        rot_mat = rotation_matrix([0, 0, 1], 0)
        molecule_protein.rotate(rot_mat)
        assert np.isclose(old_pos, molecule_protein.atoms_positions).all()

    def test_move(self, molecule_protein: Molecule):
        """
        Tests for the moving methods.
        """
        # move_to
        move_to = [0, 1, 2]
        old_pos = molecule_protein.atoms_positions
        displ = move_to - molecule_protein.geometric_center
        molecule_protein.move_to(move_to)
        assert np.isclose(move_to, molecule_protein.geometric_center).all()
        assert np.isclose(old_pos + displ, molecule_protein.atoms_positions).all()

        # move
        move = [0, 1, 2]
        old_pos = molecule_protein.atoms_positions
        old_center = molecule_protein.geometric_center
        molecule_protein.move(move)
        assert np.isclose(move + old_center, molecule_protein.geometric_center).all()
        assert np.isclose(old_pos + move, molecule_protein.atoms_positions).all()

    def test_write_gro(self, molecule_protein: Molecule, tmp_path: Path):
        """
        Test for writing gro files.
        """
        subdir = tmp_path / "molecule_test"
        subdir.mkdir()
        fgrotmp = str(subdir / "test_write_gro.gro")
        molecule_protein.write_gro(fgrotmp)
        with GroFile(fgrotmp) as fgro:
            assert fgro.natoms == 34
            for line, atom in zip(fgro, molecule_protein):
                atom_gro = AtomGro(line)
                assert atom.atom_gro == atom_gro

    def test_update_from_top(self, molecule_bmim: Molecule):
        """
        Test for update_from_molecule_top method.
        """
        # Make a copy of the original top
        fname = os.path.join(ACTUAL_PATH, '../../gaddlemaps/data/BMIM_AA.itp')
        mol_top = MoleculeTop(fname)
        for at1, at2 in zip(mol_top, molecule_bmim):
            assert at1.name == at2.name

        mol_top[1].name = 'test'
        molecule_bmim.update_from_molecule_top(mol_top)
        for at1, at2 in zip(mol_top, molecule_bmim):
            assert at1.name == at2.name
        assert molecule_bmim[1].name == 'test'

    def test_distance_to(self, molecule_protein: Molecule):
        """
        Test for method related with distances.
        """
        center = molecule_protein.geometric_center
        # Distance to zero
        assert ((center**2).sum())**.5 == molecule_protein.distance_to_zero

        # Distance to self
        assert molecule_protein.distance_to(molecule_protein) == 0
        # Distance to zero
        assert molecule_protein.distance_to([0, 0, 0]) == molecule_protein.distance_to_zero
        # Distance to zero with box vector
        box_vects = np.diag(molecule_protein.geometric_center)
        assert round(molecule_protein.distance_to([0, 0, 0],
                                                  box_vects=box_vects), 8) == 0
        # Distance to zero with box vector with inv
        inv_box = np.linalg.inv(box_vects)
        assert round(molecule_protein.distance_to([0, 0, 0], box_vects=inv_box,
                                                  inv=True), 8) == 0

    def test_copy(self, molecule_bmim: Molecule):
        """
        Test the copy method of Molecule class.
        """
        copy_mol = molecule_bmim.copy()

        assert copy_mol == molecule_bmim
        assert copy_mol.resnames == molecule_bmim.resnames

        assert copy_mol.molecule_top == molecule_bmim.molecule_top
        assert copy_mol.molecule_top is molecule_bmim.molecule_top

        assert copy_mol.residues == molecule_bmim.residues
        assert copy_mol.residues is not molecule_bmim.residues

        # Tricky change in the resname of the topology
        copy_mol.molecule_top.resnames = ['Test']
        assert molecule_bmim.molecule_top.resnames == ['Test']

    def test_deep_copy(self, molecule_bmim: Molecule):
        """
        Test the deep_copy method of Molecule class.
        """
        copy_mol = molecule_bmim.deep_copy()

        assert copy_mol == molecule_bmim
        assert copy_mol.resnames == molecule_bmim.resnames

        assert copy_mol.molecule_top == molecule_bmim.molecule_top
        assert copy_mol.molecule_top is not molecule_bmim.molecule_top

        assert copy_mol.residues == molecule_bmim.residues
        assert copy_mol.residues is not molecule_bmim.residues

        copy_mol.molecule_top[0].name = 'T2'
        copy_mol.residues[0][0].name = 'T2'
        assert molecule_bmim != copy_mol

        # Tricky change in the resname of the topology
        copy_mol.molecule_top.resnames = ['Test']
        assert molecule_bmim.molecule_top.resnames == ['BMIM']


from maya import cmds

import pyblish.api
import colorbleed.api
import colorbleed.maya.action

SUFFIX_NAMING_TABLE = {'mesh': ["_GEO", "_GES", "_GEP", "_OSD"],
                       'nurbsCurve': ["_CRV"],
                       'nurbsSurface': ["_NRB"],
                       None: ['_GRP']}

ALLOW_IF_NOT_IN_SUFFIX_TABLE = True

class ValidateTransformNamingSuffix(pyblish.api.InstancePlugin):
    """Validates transform suffix based on the type of its children shapes.

    Suffices must be:
        - mesh:
            _GEO (regular geometry)
            _GES (geometry to be smoothed at render)
            _GEP (proxy geometry; usually not to be rendered)
            _OSD (open subdiv smooth at rendertime)
        - nurbsCurve: _CRV
        - nurbsSurface: _NRB
        - null/group: _GRP

    .. warning::
        This grabs the first child shape as a reference and doesn't use the
        others in the check.

    """

    order = colorbleed.api.ValidateContentsOrder
    hosts = ['maya']
    families = ['colorbleed.model']
    category = 'cleanup'
    optional = True
    version = (0, 1, 0)
    label = 'Suffix Naming Conventions'
    actions = [colorbleed.maya.action.SelectInvalidAction]

    @staticmethod
    def is_valid_name(node_name, shape_type):
        """Return whether node's name is correct.

        The correctness for a transform's suffix is dependent on what
        `shape_type` it holds. E.g. a transform with a mesh might need and
        `_GEO` suffix.

        When `shape_type` is None the transform doesn't have any direct
        children shapes.

        """
        if shape_type not in SUFFIX_NAMING_TABLE:
            return ALLOW_IF_NOT_IN_SUFFIX_TABLE
        else:
            suffices = SUFFIX_NAMING_TABLE[shape_type]
            for suffix in suffices:
                if node_name.endswith(suffix):
                    return True
            return False

    @classmethod
    def get_invalid(cls, instance):
        transforms = cmds.ls(instance, type='transform', long=True)

        invalid = []
        for transform in transforms:
            shapes = cmds.listRelatives(transform,
                                        shapes=True,
                                        fullPath=True,
                                        noIntermediate=True)

            shape_type = cmds.nodeType(shapes[0]) if shapes else None
            if not cls.is_valid_name(transform, shape_type):
                invalid.append(transform)

        return invalid

    def process(self, instance):
        """Process all the nodes in the instance"""

        invalid = self.get_invalid(instance)
        if invalid:
            raise ValueError("Incorrectly named geometry "
                             "transforms: {0}".format(invalid))


# Combine the two programs from Exercise into one file.
# If the number is already stored, report the favorite number to the user.
# If not, prompt for the user’s favorite number and store it in a file.
# Run the program twice to see that it works.
import json
filename = "faveNumber.json"
try:
    with open(filename, 'r') as f:
        Fave_number = json.load(f)
except FileNotFoundError:
    Fave_number = input("Can you tell me what your favourite is ?: ")
    with open(filename, "w") as f:
        json.dump(Fave_number, f)
else:
    print(f"I know your favorite number! It's {Fave_number}.")


import cv2
from datetime import datetime

import PCA.PCA_math as PCA
import settings.settings as set_man
import utils.log as log_man
import camera.camera as cam_man
import utils.img as img_man
import settings.settings as set_man

# module config
_color_red = (0, 0, 255)
_color_white = (255, 255, 255)

def training_mode_setup():
    PCA.load_model()

def training_mode_loop():
    # load settings
    avg_mine_dia = set_man.get_settings('avg_mine_dia')
    cam = cam_man.get_cam()
    frame_size = set_man.get_settings('frame_size')

    # read frame from camera
    _, frame = cam.read()
    frame = cv2.resize(frame, dsize=(frame_size, frame_size), interpolation=cv2.INTER_CUBIC)
    grey_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    # check if there is object in the middle of the frame
    is_mid = img_man.is_obj_in_middle(grey_frame)

    mid_obj_x = int(frame_size / 2)
    mid_obj_y = int(frame_size / 2)

    # highlight middle object
    if (is_mid):
        frame = cv2.circle(frame, (mid_obj_x, mid_obj_y), 2, _color_red, -1)
        frame = cv2.circle(frame, (mid_obj_x, mid_obj_y),
                           int(avg_mine_dia / 2), _color_red, 2)
    else:
        frame = cv2.circle(frame, (mid_obj_x, mid_obj_y), 2, _color_white, -1)
        frame = cv2.circle(frame, (mid_obj_x, mid_obj_y),
                           int(avg_mine_dia / 2), _color_white, 2)

    # show modified frame
    cv2.imshow('Robot Camera', frame)

    # read key hit
    key = cv2.waitKey(1) & 0xFF

    # process key hit
    if (key == ord('s')):
        # crop central image
        cor_image = grey_frame[int(mid_obj_x - (avg_mine_dia) / 2):int(mid_obj_x + ((avg_mine_dia) / 2)),
                               int(mid_obj_y - (avg_mine_dia) / 2):int(mid_obj_y + ((avg_mine_dia) / 2))]

        # save image to the dataset directory
        cur_img_count: int = set_man.get_settings('next_img_index')
        img_dir = f"./dataset/img{cur_img_count}.jpg"
        set_man.set_settings('next_img_index', cur_img_count + 1)
        cv2.imwrite(img_dir, cor_image)

        log_man.add_log('modes.training_mode_loop',
                        'DEBUG', f"added image {img_dir} to dataset")

    elif (key == ord('t')):
        # crop central image
        cor_image = grey_frame[int(mid_obj_x - (avg_mine_dia) / 2):int(mid_obj_x + ((avg_mine_dia) / 2)),
                               int(mid_obj_y - (avg_mine_dia) / 2):int(mid_obj_y + ((avg_mine_dia) / 2))]

        # test the model
        img_name = PCA.match_image(cor_image)
        print(img_name)
        input()

    elif (key == ord('r')):
        # train the model
        log_man.add_log('modes.training_mode_loop',
                        'INFO', f"fitting PCA model to the dataset")
        PCA.train_model()
        log_man.add_log('modes.training_mode_loop',
                        'INFO', f"done fitting PCA model to the dataset")

        set_man.save_settings()

        return -1

    elif (key == ord('q')):
        set_man.save_settings()
        return -1

    return 0


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

# In[1]:

# Import the libraries
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt  # for 畫圖用
import pandas as pd# load and evaluate a saved model
from numpy import loadtxt
from tensorflow.keras.models import load_model
from tensorflow.keras.models import Sequential, Model
from tensorflow.keras.layers import Dense, Dropout, Activation, Flatten, LSTM, TimeDistributed, RepeatVector,GRU, Input, ConvLSTM2D, Bidirectional,BatchNormalization,Bidirectional
from tensorflow.keras import Input
#from tensorflow.keras.layers.normalization import BatchNormalization
from tensorflow.keras import regularizers
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint
from sklearn.metrics import mean_squared_error
import math
import json
from IPython.core.pylabtools import figsize
from scipy.fft import fft, ifft

figsize(15, 7)
DATASET_NAME  = 'TJNEWM_HEAP_MEM_USED_85%MAX_2021_Jan&Feb.csv'

#dataset_train = pd.read_csv('./dataset/THGBNKLAK1TS301_Memory Used (percentage)_Oct_2020.csv')
dataset_train = pd.read_csv('./dataset/'+DATASET_NAME)
#dataset_test = pd.read_csv('./dataset/ATKH_Oplus_TWGKHHPSK1MSB04_memory_usage_2020_11.csv')
dataset_train = dataset_train.reindex(index=dataset_train.index[::-1])
#dataset_test = dataset_test.reindex(index=dataset_test.index[::-1])
training_set = dataset_train.iloc[:40920,3:4].values
testing_set = dataset_train.iloc[40920:,3:4].values
#plt.plot(training_set)

# In[2]:

def solve_cudnn_error():
    gpus = tf.config.experimental.list_physical_devices('GPU')
    if gpus:
        try:
            # Currently, memory growth needs to be the same across GPUs
            for gpu in gpus:
                tf.config.experimental.set_memory_growth(gpu, True)
            logical_gpus = tf.config.experimental.list_logical_devices('GPU')
            print(len(gpus), "Physical GPUs,", len(logical_gpus), "Logical GPUs")
        except RuntimeError as e:
            # Memory growth must be set before GPUs have been initialized# Memory growth must be set before GPUs have been initialized
            print(e)

# In[3]:

#print(testing_set.shape)
solve_cudnn_error()

# In[4]:

# Feature Scaling
from sklearn.preprocessing import MinMaxScaler,RobustScaler

#MinMaxScaler

#sc_train = MinMaxScaler(feature_range = (0, 1))
#training_set_scaled = sc_train.fit_transform(training_set)

#sc_test = MinMaxScaler(feature_range = (0, 1))
#testing_set_scaled = sc_test.fit_transform(testing_set)

#RobustScaler

sc_train = RobustScaler()
training_set_scaled = sc_train.fit_transform(training_set)

sc_test = RobustScaler()
testing_set_scaled = sc_test.fit_transform(testing_set)

# In[9]:

SLIDE_WIN_SIZE = 60
def gen_dataset(data,x_window_size,y_window_size):
    X_train = []   #預測點的前 60 天的資料
    y_train = []   #預測點
    for i in range(x_window_size, data.shape[0]-y_window_size,y_window_size):
        X_train.append(data[i-x_window_size:i, :])
        y_train.append(data[i:i+y_window_size, 0])

    X_train, y_train = np.array(X_train), np.array(y_train)  # 轉成numpy array的格式，以利輸入 RNN

    X_train = np.reshape(X_train, (X_train.shape[0], X_train.shape[1], 1))

    return X_train,y_train

X_train,y_train = gen_dataset(training_set_scaled,SLIDE_WIN_SIZE,1)
X_test,y_test = gen_dataset(testing_set_scaled,SLIDE_WIN_SIZE,1)

# In[10]:

print(X_train.shape)
print(y_train.shape)
print(X_test.shape)
print(y_test.shape)

# In[359]:

from tensorflow.keras.callbacks import ReduceLROnPlateau, ModelCheckpoint

rp = ReduceLROnPlateau(
    monitor='loss',
    factor=0.1,
    patience=5,
    verbose=1,
    mode='auto',
    min_delta=0.0001,
    cooldown=0,
    min_lr=0
)
filepath = "./model/NXP_model/NXP_best_model.h5"
checkpoint = ModelCheckpoint(filepath, monitor='loss', verbose=1, save_best_only=True,
mode='max')

# In[360]:

#from keras import losses
model = Sequential()

model.add(LSTM(128,input_shape=(X_train.shape[1], X_train.shape[2]),return_sequences=False,unroll=False))
model.add(Dense(y_train.shape[1]))
optz_fun = Adam(learning_rate=1e-4)
model.compile(loss='mae', optimizer=optz_fun)

model.summary()

# In[361]:

# In[362]:

model.layers[0].get_weights()[1]

# In[363]:

EPOCH = 50
BATCH = 128

# In[364]:

history = model.fit(X_train, y_train, epochs=EPOCH, batch_size=BATCH, validation_data=(X_test,y_test),callbacks=[rp,checkpoint],shuffle=False,verbose=1)

# In[357]:

plt.plot(history.history['loss'],label="Train loss")
plt.plot(history.history['val_loss'],label="Test loss")
plt.title('Model loss')
plt.ylabel('Loss')
plt.xlabel('Epoch')
#plt.ylim(0,10)
plt.legend(loc='upper left')
plt.show()

# In[358]:

predicted_traffic = model.predict(X_test)

#predicted_traffic = sc_train.inverse_transform(predicted_traffic)  # to get the original scale
predicted_traffic = sc_test.inverse_transform(predicted_traffic)
#predicted_traffic = predicted_traffic.astype('int64')

#plot_predicted_traffic = np.reshape(predicted_traffic,(-1,1))

# In[326]:

show_data_count = 0

plt.plot(testing_set[SLIDE_WIN_SIZE+1:],color = 'red', label = 'Real MEM Used')
plt.plot(predicted_traffic,color = 'blue', label = 'Predicted MEM Used')

#plt.axhline(y=60, xmin=0, xmax=predicted_traffic.shape[0],label = 'upper')
#plt.axhline(y=30, xmin=0, xmax=predicted_traffic.shape[0],label = 'low')
plt.xlabel('Sequence')
plt.ylabel('24H Detection Trend Values')
plt.title(DATASET_NAME)
plt.legend()
plt.show()

# In[327]:

from sklearn.metrics import mean_squared_error
import math
from sklearn.metrics import mean_absolute_error
from  datetime import datetime

now_time = datetime.now()     #獲取當前時間
now_time = now_time.strftime('%Y%m%d_%H%M%S')   #列印需要的資訊,依次是年月日,時分秒,注意字母大小寫
print ("\nResult:")

print("-------------------------------")
#MAEScore_array = np.mean(abs(testing_set[61:]-predicted_traffic))
MAEScore = mean_absolute_error(testing_set[SLIDE_WIN_SIZE+1:],predicted_traffic)
RMSEScore = math.sqrt(mean_squared_error(testing_set[SLIDE_WIN_SIZE+1:],predicted_traffic))
#print(MAEScore_array)

print('Test Score: %.2f MAE' % (MAEScore))
print('Test Score: %.2f RMSE' % (RMSEScore))
print("-------------------------------")

"""
anomaly_index = dataset_test.loc[dataset_test['value'] >= 60, ['timestamp', 'value']].index
anomaly_value = dataset_test.loc[dataset_test['value'] >= 60, ['value']].values
anomaly_MAEScore = mean_absolute_error(predicted_traffic[anomaly_index],anomaly_value)
anomaly_RMSEScore = math.sqrt(mean_squared_error(predicted_traffic[anomaly_index],anomaly_value))

print('Anomaly_Test Score: %.2f MAE' % (anomaly_MAEScore))
print('Anomaly_Test Score: %.2f RMSE' % (anomaly_RMSEScore))

print("-------------------------------")
"""
"""
from tensorflow.keras.utils import plot_model
plot_model(model, to_file='./model_plot/LSTM_AWS_model.png',show_shapes=True)
"""
#tf.saved_model.save(model, "Zabbix")
model.save("./model/NXP_model/Zabbix_model.h5")

# In[195]:

diff_list = []
for i in range(predicted_traffic.shape[0]):
    #plt.plot(predicted_traffic[i:i+1][0]-predicted_traffic[i-1][0])
    #print(predicted_traffic[i]-predicted_traffic[i-1])

    diff_list.append(abs(predicted_traffic[i]-predicted_traffic[i-1]))
plt.plot(diff_list)
plt.show()

# In[29]:

plt.plot(predicted_traffic)
plt.show()


"""
Knoxville Divergence

Definition The Knoxville Divergence indicator suggests that price will reverse in direction, making it a
counter-trend indicator. For example, if price is in an uptrend with the Knoxville Divergence line set above the
price on the chart, the trader will understand that the indicator is suggesting price will potentially reverse and
shift to a downtrend shortly. This indicator is very useful for counter-trend traders and general trend-traders
alike, as it adds perspective for those seeking to enter a trade, while following a long-term trend.

History This indicator is used and admired by Rob Booker, an experienced entrepreneur and currency trader who has
helped bring popularity to the indicator.

Takeaways
When analyzing Knoxville Divergence, it should be known that the indicator is comprised of two main elements.

The Momentum indicator. If price is rising, but the momentum indicator is falling, the Knoxville Divergence indicator
might appear on the chart to signal to the trader that a price reversal may be closing in. This indicator must be
paired with another, however, because it does not tell the trader everything they need to know. This is why it pairs
nicely with our second element.

Relative Strength Index. If there is a simultaneous discrepancy between price and the momentum indicator,
the Relative Strength Index (RSI) will signal as overbought or oversold, and will trigger the Knoxville Divergence
line to appear on the chart.

Script:
First, we need to compute the Momentum indicator. The Momentum of a price is simply the difference in price over

the specified period. In this case, the period is 18.

Next, we will calculate the Relative Strength Index (RSI). The RSI is a bit more complex. It measures the speed and
change of price movements and is often used to identify overbought or oversold conditions in a market. When the RSI
for a security gets above 70, it could indicate that the asset is overbought, and when it gets below 30,
it could indicate the asset is oversold. """

import matplotlib.pyplot as plt
import ta
import yfinance as yf

def get_knoxville_divergence(data, period=21):
    # Calculate Momentum and RSI
    data['momentum'] = ta.momentum.AwesomeOscillatorIndicator(high=data['High'], low=data['Low'], window1=5, window2=period).awesome_oscillator()
    data['rsi'] = ta.momentum.RSIIndicator(close=data['Close'], window=period).rsi()

    # Initialize Knoxville Divergence series with 0
    data['knoxville_divergence'] = 0

    # Identify Bullish/Bearish Divergence
    for i in range(period, len(data)):
        # Bullish Divergence
        if data['Close'].iloc[i] > data['Close'].iloc[i-period] and data['momentum'].iloc[i] < data['momentum'].iloc[i-period] and data['rsi'].iloc[i] < 30:
            data['knoxville_divergence'].iloc[i] = -1
        # Bearish Divergence
        elif data['Close'].iloc[i] < data['Close'].iloc[i-period] and data['momentum'].iloc[i] > data['momentum'].iloc[i-period] and data['rsi'].iloc[i] > 70:
            data['knoxville_divergence'].iloc[i] = 1

    return data

# Fetch data from Yahoo Finance
data = yf.download('PLTR', start='2022-01-01', end='2023-01-01', interval='1d')

# Calculate Knoxville Divergence
data = get_knoxville_divergence(data)

# Plot the Close price and the Knoxville Divergence
fig, ax1 = plt.subplots()

color = 'tab:blue'
ax1.set_xlabel('Date')
ax1.set_ylabel('Close', color=color)
ax1.plot(data.index, data['Close'], color=color)
ax1.tick_params(axis='y', labelcolor=color)

ax2 = ax1.twinx()  # instantiate a second axes that shares the same x-axis
color = 'tab:red'
ax2.set_ylabel('Knoxville Divergence', color=color)
ax2.plot(data.index, data['knoxville_divergence'], color=color)
ax2.tick_params(axis='y', labelcolor=color)

plt.title('PLTR Close Price and Knoxville Divergence')
fig.tight_layout()  # otherwise the right y-label is slightly clipped
plt.show()


class Solution:
    def findSubstring(self, s, words):
        """
        做不来
        :param s:
        :param words:
        :return:
        """
        if not s or not words:
            return []

        lenth = len(words[0]) * len(words)
        word_len = len(words[0])
        from collections import Counter
        words_hash = Counter(words)
        res = []
        for i in range(0, len(s) - lenth + 1):
            tmp = s[i:i + lenth]
            # print(tmp)
            c_tmp = []
            for j in range(0, lenth, word_len):
                c_tmp.append(tmp[j:j+word_len])
            # print(c_tmp)
            c_tem_hash = Counter(c_tmp)
            if c_tem_hash == words_hash:
                res.append(i)
        return res

if __name__ == "__main__":
    str = "barfoothefoobarman"
    words = ["foo", "bar"]
    s = Solution()
    print(s.findSubstring(str, words))


from typing import Tuple
import tensorflow as tf

# The goal is to calculate the top K highest relevant negative samples from very large dataset
# but given scenarios K = 10, the top K might have overlapse ids with the positive sample
# given ids of positive samples on each batch row, remove overlapse ids if exists

# A query embedding will be passed into stream
queries = None

# top 10
k = 10

# Given candidates = movies.batch(128).map(
#       lambda x: (x["movie_id"], movie_model(x["movie_title"]))
# )
# this function will be used as a another mapper to candidate
# notice that output indices will be 2 dimension
def top_scores(candidate_index: tf.Tensor,
                candidate_batch: tf.Tensor) -> Tuple[tf.Tensor, tf.Tensor]:

    scores = tf.linalg.matmul(queries, candidate_batch, transpose_b=True)

    k_ = k

    scores, indices = tf.math.top_k(scores, k=k_, sorted=True)

    return scores, tf.gather(candidate_index, indices)

# top_scores will produce multiple batch of tuple(score, identity)
# this function will be used as reduce, to reduce result to only 1 tuple of top k
def top_k(state: Tuple[tf.Tensor, tf.Tensor],
            x: Tuple[tf.Tensor, tf.Tensor]) -> Tuple[tf.Tensor, tf.Tensor]:

    state_scores, state_indices = state
    x_scores, x_indices = x

    joined_scores = tf.concat([state_scores, x_scores], axis=1)
    joined_indices = tf.concat([state_indices, x_indices], axis=1)

    k_ = k

    scores, indices = tf.math.top_k(joined_scores, k=k_, sorted=True)

    return scores, tf.gather(joined_indices, indices, batch_dims=1)

# You can use this function instead of tf.gather(identifiers, indices, batch_dims=1)
def _take_along_axis(arr: tf.Tensor, indices: tf.Tensor) -> tf.Tensor:
  """Partial TF implementation of numpy.take_along_axis.

  See
  https://numpy.org/doc/stable/reference/generated/numpy.take_along_axis.html
  for details.

  Args:
    arr: 2D matrix of source values.
    indices: 2D matrix of indices.

  Returns:
    2D matrix of values selected from the input.
  """

  row_indices = tf.tile(
      tf.expand_dims(tf.range(tf.shape(indices)[0]), 1),
      [1, tf.shape(indices)[1]])
  gather_indices = tf.concat(
      [tf.reshape(row_indices, (-1, 1)),
       tf.reshape(indices, (-1, 1))], axis=1)

  return tf.reshape(tf.gather_nd(arr, gather_indices), tf.shape(indices))

# 10 + 1 positive samples
# First step would be calculate top 11 scores,
# then reduce the ids if exists and recalculate top 10 scores
adjusted_k = 11

batch_size = 5

# Seed data for testing, ensure data format
scores = tf.reshape(tf.cast(tf.range(50, (adjusted_k * 5) + 50), tf.float32), shape=(batch_size, adjusted_k))
identifiers = tf.reshape(tf.range(adjusted_k * 5), shape=(batch_size, adjusted_k))

def exclude():
    # Seed ids need to be excluded
    exclude = tf.constant([
        [2],
        [15],
        [27],
        [33],
        [47],
    ])

    idents = tf.expand_dims(identifiers, -1)
    exclude = tf.expand_dims(exclude, 1)

    isin = tf.math.reduce_any(tf.math.equal(idents, exclude), -1)

    # Set the scores of the excluded candidates to a very low value.
    adjusted_scores = (scores - tf.cast(isin, tf.float32) * 1.0e5)

    # Make sure k is smaller or equal scores size
    k_ = tf.math.minimum(k, tf.shape(scores)[1])

    _, indices = tf.math.top_k(adjusted_scores, k_)

    # produce the same result
    print(tf.gather(identifiers, indices, batch_dims=1))
    print(_take_along_axis(identifiers, indices))

exclude()


# encoding: utf-8
# Created on 2014-5-27
# @author: binge

from hashlib import sha1
from tenjin.helpers import *
from tornado.web import RequestHandler, HTTPError
from utils import settings, conns_helper
from utils.common import TimeHelper
import json
import os
import tenjin
import time
import traceback
import uuid
import service
from tornado import log

tenjin_engine = tenjin.Engine(pp=[ tenjin.TrimPreprocessor() ], layout=os.path.join(os.path.dirname(__file__), "views/_layout.html"))

class BaseHandler(RequestHandler):

    MTHOD_NOT_ALLOWED = 405
    URL_NOT_FOUND = 404
    SERVER_ERROR = 500

    _skip_attrs = ['get', 'post']

    _redis = conns_helper.get_redis_conn(db=settings.SESSION_REDIS_DB)

    def initialize(self, init_context=True):
        self.user_service = service.user_serv
        self.context = self.__init_context(init_context)

    def __init_context(self, init_context):
        context = {}
        if init_context:
            context['now_time'] = TimeHelper.time_2_str()
        return context

    def gene_html(self, template_name_prefix, template_name_suffix='.html', layout=True):
        return tenjin_engine.render(os.path.join(os.path.dirname(__file__), template_name_prefix + template_name_suffix), self.context, layout=layout)

    def render(self, template_name_prefix, template_name_suffix='.html', layout=True):
        self.write(self.gene_html(template_name_prefix, template_name_suffix, layout))
#         self.finish()

    def raise_http_error(self, err_code, err_info=None):
        if not err_info:
            err_info = traceback.format_exc()
        if err_info:
            self.log_err(err_info)
        if 'X-Requested-With' in self.request.headers and 'XMLHttpRequest' == self.request.headers['X-Requested-With']:
            self.set_status(err_code)
            self.finish()
        else:
            self.context['err_code'] = err_code
            self.render('views/err', layout=False)

    def ajax_result(self, result):
        #self.set_header('Content-Type', 'application/json; charset=UTF-8')
        self.write(json.dumps(result, ensure_ascii=False))
#         self.finish()

    def list_parms(self, params, empty_err_code=None):
        if not params:
            if empty_err_code:
                self.raise_http_error(empty_err_code)
            return []
        return params.split('/')

    def __set_session_attr__(self, name, value):
        Session(self).__setattr__(name, value)

    def __get_session_attr__(self, name):
        return Session(self).__getattr__(name)

    def __del_session_attr__(self, name):
        Session(self).__delattr__(name)

    def __destory_session__(self):
        Session(self).__destory__()

    def log_err(self, err):
        log.access_log.error(err)

class Session(object):
    _prefix = "_session:"
    _id = None
    _skip = ['_redis', '_request', '_id']
    def __init__(self, request):
        self._redis = request._redis
        self._request = request
        # init session id
        _id = request.get_secure_cookie('sessionid')
        if _id and self._redis.exists(_id):
            self._id = _id

    def init_session(self):
        """初始化"""
        if not self._id:
            self._id = self.generate_session_id()
            self._request.set_secure_cookie('sessionid', self._id)
        self.__set_expire()

    def generate_session_id(self):
        """Generate a random id for session"""
        secret_key = self._request.settings['cookie_secret']
        ip = self._request.request.remote_ip
        while True:
            rand = os.urandom(16)
            now = time.time()
            sessionid = sha1("%s%s%s%s" % (rand, now, ip, secret_key))
            sessionid = self._prefix + sessionid.hexdigest()
            if not self._redis.exists(sessionid):
                break
        return sessionid

    def __set_expire(self):
        # 延期过期时间
        self._redis.hset(self._id, 'lastActive', time.time())
        self._redis.expire(self._id, settings.SESSION_MAXLIFETIME)

    def __getattr__(self, name):
        if self._id:
            self.__set_expire()
            return self._redis.hget(self._id, name)
        if not name in self._skip:
            return None
        return object.__getattribute__(self, name)

    def __setattr__(self, name, value):
        if not name in self._skip:
            self.init_session()
            self._redis.hset(self._id, name, value)
        object.__setattr__(self, name, value)

    def __delattr__(self, name):
        if not name in self._skip:
            return self._redis.hdel(self._id, name)
        object.__delattr__(self, name)

    def __destory__(self):
        self._redis.delete(self._id)

def auto_login(require_login=True):
    def wrapper(fn):
        def login(request, *args):
            user = request.__get_session_attr__('user')
            if not user:
                user_id = request.get_secure_cookie('user_id')
                save_me = None
#                 save_me = request.get_secure_cookie('save_me')
                if user_id and save_me:
                    user = request.user_service.find_one({'_id' : user_id})
                    if user:
                        request.__set_session_attr__('user', user)
            else:
                user = eval(user)
            if user:
                request.current_user = user
                request.context['curr_user'] = user
            else:
                if require_login:
                    if 'X-Requested-With' in request.request.headers and 'XMLHttpRequest' == request.request.headers['X-Requested-With']:
                        request.set_status(403)
                        request.finish()
                    else:
                        request.redirect("/auth/login")
                    return None
            return fn(request, *args)
        return login
    return wrapper

def logout():
    def wrapper(fn):
        def _do(request, *args):
            request.__destory_session__()
            request.set_secure_cookie('save_me', '', 0)
            request.set_secure_cookie('user_id', '', 0)
            return fn(request, *args)
        return _do
    return wrapper

def except_err():
    def wrapper(fn):
        def _except(request, *args):
            try:
                return fn(request, *args)
            except:
                request.raise_http_error(request.SERVER_ERROR, traceback.format_exc())
            finally:
                pass
        return _except
    return wrapper

class ErrHandler(BaseHandler):

    def get(self, res):
        if res:
            try:
                fns = os.path.dirname(__file__).split(os.sep)[:-1]
                fns.append(res)
                if os.path.exists(os.sep.join(fns)):
                    rf = open(os.sep.join(fns))
                    self.finish(rf.read())
                else:
                    self.write('go fuck yourself!')
            except:
                self.raise_http_error(self.URL_NOT_FOUND)
            finally:
                return
        self.raise_http_error(self.URL_NOT_FOUND)

    def post(self, res):
        self.get()


# -*- coding: utf8 -*-

# Standard library imports
from itertools import chain
import os
import sys
import time

# PyPI library imports
import toml
import numpy as np

# Local library imports
from dmppl.base import dbg, info, verb, joinP, tmdiff, rdTxt

# Project imports
# NOTE: Roundabout import path for eva_common necessary for unittest.
from dmppl.experiments.eva.eva_common import \
    appPaths, paths, metricNames, cfgDsfDeltas, loadCfg, evaLink
from dmppl.experiments.eva.eva_html_table import \
    calculateTableData, htmlTable, evaTitleText
from dmppl.experiments.eva.eva_svg_netgraph import \
    calculateEdges, svgNetgraph

# Version-specific imports
version_help = "Python 2.7 or 3.4+ required."
if sys.version_info[0] == 2:
    assert sys.version_info[1] >= 7, version_help
    from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
    from urlparse import parse_qs
elif sys.version_info[0] == 3:
    assert sys.version_info[1] >= 4, version_help
    from http.server import BaseHTTPRequestHandler, HTTPServer
    from urllib.parse import parse_qs
else:
    assert False, version_help

class EvaHTMLException(Exception): # {{{
    pass
# }}} class EvaHTMLException

def htmlTopFmt(body, inlineJs=True, inlineCss=True, bodyOnly=False): # {{{
    '''Return a string with HTML headers for JS and CSS.
    '''

    fnamesJs = (joinP(appPaths.share, fname) for fname in \
                ("jquery-3.3.1.slim.min.js",
                 "bootstrap-3.3.7.min.js",
                 "eva.js"))

    fnamesCss = (joinP(appPaths.share, fname) for fname in \
                 ("bootstrap-3.3.7.min.css",
                  "eva.css"))

    jsTxts = (('' % rdTxt(fname)) \
              if inlineJs else \
              ('' % fname)
              for fname in fnamesJs)

    cssTxts = (('' % rdTxt(fname)) \
               if inlineCss else \
               ('<link rel="stylesheet" type="text/css" href="%s">' % fname)
               for fname in fnamesCss)

    ret = '\n'.join(body) \
        if bodyOnly else \
        '\n'.join(r.strip() for r in (
          '<!DOCTYPE html>',
          '',
          '  ',
          '\n'.join(chain(jsTxts, cssTxts)),
          '  ',
          '  ',
          '\n'.join(body),
          '  ',
          '',
        ))
    return ret
# }}} def htmlTopFmt

def evaHtmlString(args, cfg, request): # {{{
    '''Return a string of HTML.

    a     b     -->
    None  None  Default values
    None  Func  1D color, swap a,b
    Func  None  1D color
    Func  Func  2D color

    u     x     y     -->
    None  None  None  Default values
    None  None  Metr  Default u
    None  Metr  None  Default u
    None  Metr  Metr  Table varying u over rows, delta over columns
    Int   None  None  Network graph
    Int   None  Metr  Table varying x over rows, delta over columns
    Int   Metr  None  Table varying y over rows, delta over columns
    Int   Metr  Metr  Ignore u
    '''
    a, b, u, x, y = \
        request['a'], request['b'], request['u'], request['x'], request['y']

    # In debug mode (without `python -O`) assertions are caught before an
    # Exception can be raised giving a 404.
    if a is None and b is None:
        # Default values
        a = metricNames[0]

    elif a is None and isinstance(b, str):
        # 1D color
        assert b in metricNames, b

        if b not in metricNames:
            raise EvaHTMLException

        a, b = b, a
    elif isinstance(a, str) and b is None:
        # 1D color
        assert a in metricNames, a

        if a not in metricNames:
            raise EvaHTMLException

    elif isinstance(a, str) and isinstance(b, str):
        # 2D color
        assert a in metricNames, a
        assert b in metricNames, b

        if a not in metricNames:
            raise EvaHTMLException

        if b not in metricNames:
            raise EvaHTMLException

    else:
        assert False

    vcdInfo = toml.load(paths.fname_meainfo)

    if u is None and x is None and y is None:
        # Default values
        tableNotNetwork = True
        u = 0
        x = vcdInfo["unitIntervalVarNames"][0]

    elif u is None and isinstance(x, str) and isinstance(y, str):
        # Table varying u over rows, delta over columns
        tableNotNetwork = True

    elif u is None and (isinstance(x, str) or isinstance(y, str)):
        # Default u
        tableNotNetwork = True
        u = 0

    elif isinstance(u, str) and isinstance(x, str) and isinstance(y, str):
        # Ignore u
        tableNotNetwork = True
        u = None

    elif isinstance(u, str) and x is None and y is None:
        # Network graph
        tableNotNetwork = False

        u = int(u)
        assert 0 <= u, u

        if 0 > u:
            raise EvaHTMLException

    elif isinstance(u, str) and x is None and isinstance(y, str):
        # Table varying x over rows, delta over columns
        tableNotNetwork = True

        u = int(u)
        assert 0 <= u, u

        if 0 > u:
            raise EvaHTMLException

    elif isinstance(u, str) and isinstance(x, str) and y is None:
        # Table varying y over rows, delta over columns
        tableNotNetwork = True

        u = int(u)
        assert 0 <= u, u

        if 0 > u:
            raise EvaHTMLException

    else:
        assert False

    # Every view varies delta - tables by horizontal, networks by edges.
    dsfDeltas = cfgDsfDeltas(cfg) # [(<downsample factor>, <delta>), ...]

    # Sort by delta value, not by downsampling factor.
    dsfDeltas.sort(key=lambda dsf_d: dsf_d[1])

    # Avoid inline JS or CSS for browser caching, but use for standalone files.
    inlineHead = (args.httpd_port == 0)

    # Specific case for returning standalone SVG.
    bodyOnly = (args.httpd_port == 0) and not tableNotNetwork

    if tableNotNetwork:
        xEx, yEx, fnUXY, varCol = \
            calculateTableData(a, b, u, x, y,
                               cfg, dsfDeltas, vcdInfo)

        _exSibRow, exSib = \
            (np.empty((1, 0)),
             np.concatenate((xEx, yEx), axis=1)) if x and y else \
            (xEx, yEx) if x else \
            (yEx, xEx)

        assert _exSibRow.shape[0] == 1, _exSibRow.shape
        exSibRow = [float(v) for v in _exSibRow[0]]

        body = htmlTable(a, b, u, x, y,
                         cfg, dsfDeltas, vcdInfo,
                         exSibRow, exSib, varCol, fnUXY)

    else:
        edges = calculateEdges(a, b, u,
                               cfg, dsfDeltas, vcdInfo)

        body = svgNetgraph(u, cfg, vcdInfo, edges) \
            if bodyOnly else \
            htmlNetgraph(a, b, u, cfg, vcdInfo, edges)

    return htmlTopFmt(body, inlineHead, inlineHead, bodyOnly)
# }}} def evaHtmlString

def htmlNetgraph(a, b, u, cfg, vcdInfo, edges): # {{{
    winStride = cfg.windowsize - cfg.windowoverlap

    ret_ = []
    ret_.append('
')
    ret_.append(  '')
    ret_.append(     evaTitleText(a, b, u, None, None))
    ret_.append(  '')
    ret_.append('
')
    ret_.append('
')
    ret_.append(  '')
    ret_.append(     evaLink(a, b, u - winStride, None, None, "prev"))
    ret_.append(     evaLink(a, b, u + winStride, None, None, "next"))
    ret_.append(  '')
    ret_.append('
')
    ret_.append('
')
    ret_ += svgNetgraph(u, cfg, vcdInfo, edges)
    ret_.append('
')
    return ret_
# }}} def htmlNetgraph

class EvaHTTPServer(HTTPServer): # {{{
    def serve_forever(self, args, cfg):
        self.RequestHandlerClass.args = args
        self.RequestHandlerClass.cfg = cfg
        HTTPServer.serve_forever(self)
# }}} class EvaHTTPServer

class EvaHTTPRequestHandler(BaseHTTPRequestHandler): # {{{

    # These are initialized by EvaHTTPServer.serve_forever()
    args, cfg = None, None

    def parseGetRequest(self, path): # {{{
        '''Parse and sanitize GET path.
        '''
        parsed = parse_qs(path.strip("/?")) # Parse query string.
        ret = {k: (parsed[k][0] if k in parsed.keys() else None) \
               for k in ('a', 'b', 'u', 'x', 'y')}
        return ret
    # }}} def parseGetRequest

    def do_GET(self): # {{{

        # Remove leading / which is usually (always?) present.
        self.path = self.path.lstrip('/')

        # Send response.
        if len(self.path) and not self.path.startswith("?") and \
            (self.path.endswith(".css") or self.path.endswith(".js")):

            # Strip off any relative paths to only find CSS and JS files.
            fname = joinP(appPaths.share, os.path.basename(self.path))

            try:
                response = rdTxt(fname)

                self.send_response(200)

                if fname.endswith(".js"):
                    self.send_header("Content-Type",
                                     "application/javascript; charset=utf-8")
                elif fname.endswith(".css"):
                    self.send_header("Content-Type",
                                     "text/css; charset=utf-8")

                responseBytes = response.encode("utf-8")

            except:
                self.send_error(404, "Invalid JS or CSS GET request!")
                return

        elif len(self.path) and self.path.endswith("favicon.ico"):
            # Bug in Chrome requests favicon.ico with every request until it
            # gets 404'd.
            # https://bugs.chromium.org/p/chromium/issues/detail?id=39402
            try:
                faviconFpath = joinP(appPaths.share, "eva_logo.png")
                responseBytes = open(faviconFpath, 'rb').read()

                self.send_response(200)
                self.send_header("Content-Type", "image/x-icon")
            except:
                self.send_error(404, "Cannot read favicon!")

        elif len(self.path) and not self.path.startswith("?"):
            # Unknown requests.
            self.send_response(404)
            return

        else:

            # Generate HTML string and send OK if inputs are valid.
            try:

                request = self.parseGetRequest(self.path)

                verb("Calculating {a,b}(x|y;u) <-- {%s,%s}(%s|%s;%s)..." % (
                    request['a'],
                    request['b'],
                    request['x'],
                    request['y'],
                    request['u'],
                ), end='')
                response = evaHtmlString(self.args, self.cfg, request)
                verb("DONE")

                self.send_response(200)
                self.send_header("Content-Type", "text/html; charset=utf-8")

                responseBytes = response.encode("utf-8")

            except EvaHTMLException:
                self.send_error(404, "Invalid GET request!")
                return

        # Send HTTP headers.
        # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers
        self.send_header("Content-Length", "%d" % len(responseBytes))
        self.end_headers()

        # Send HTML.
        self.wfile.write(responseBytes)

        return
    # }}} def do_GET

    def log_message(self, *fnArgs): # {{{
        if self.args.info:
            BaseHTTPRequestHandler.log_message(self, *fnArgs)
    # }}} def log_message

# }}} class EvaHTTPRequestHandler

def runHttpDaemon(args, cfg): # {{{
    '''Run local HTTP/HTML daemon serving data visualization pages on request.
    '''

    verb("Starting HTTPD on TCP port %d..." % args.httpd_port, end='')

    httpd = EvaHTTPServer(('', args.httpd_port), EvaHTTPRequestHandler)

    verb("Running...")

    try:
        tm_start = time.time()
        httpd.serve_forever(args, cfg)
    except KeyboardInterrupt:
        tm_stop = time.time()
        verb("Stopped HTTPD server [%s]" % \
            tmdiff(tm_stop - tm_start))

    return
# }}} def runHttpDaemon

def evaHttpd(args): # {{{
    '''Read in result directory like ./foo.eva/ and serve HTML visualizations.
    '''
    assert paths._INITIALIZED

    try:
        cfg = loadCfg()

        if 0 != args.httpd_port:
            runHttpDaemon(args, cfg)
        else:
            request = {'a': args.a,
                       'b': args.b,
                       'u': args.u,
                       'x': args.x,
                       'y': args.y}
            print(evaHtmlString(args, cfg, request))
    except IOError as e:
        msg = "IOError: %s: %s\n" % (e.strerror, e.filename)
        sys.stderr.write(msg)

    return 0
# }}} def evaHttpd

if __name__ == "__main__":
    assert False, "Not a standalone script."


from typing import List, Tuple

from loguru import logger

from .agent import Agent
from .world import World

class Game:
    """
    Models a simple game where an agent tries to solve a grid world with the given configuration

    Parameters
    ----------
    world: gridworld.models.World
        Board to be solved.
    agent: gridworld.models.Agent
        Agent that will solve the board.
    """

    def __init__(self, world_width: int, world_height: int, start_cell: int, goal_cell: int,
                 obstacles_cells: List[int]) -> None:
        """

        Parameters
        ----------
        world_width: int
            World's width in cells.
        world_height: int
            World's height in cells.
        start_cell: int
            Cell where the agent will start.
        goal_cell: int
            Cell where the agent has to go.
        obstacles_cells: list
            Cells where obstacles will be placed.
        """

        self.world = World(grid_width=world_width, grid_height=world_height, starting_position=start_cell,
                           goal_position=goal_cell, obstacle_positions=obstacles_cells)

        self.agent = Agent()

    def play(self, policy_search_iterations: int = 200000, value_search_iterations: int = 100000,
             threshold: float = 1e-20, gamma: float = 0.8) -> Tuple[list, bool]:
        """
        Makes agent solve the board.

        Parameters
        ----------
        policy_search_iterations: int
            Maximum number of iterations when looking for optimal policy.
        value_search_iterations: int
            Maximum number of iterations when looking for optimal state-value function.
        threshold: float
            Minimum change that should happen to continue value search iteration.
        gamma: float
            Discount factor

        Returns
        -------
        player_positions: list
            Cells the agent followed.
        reached_goal: bool
            True if agent reached goal cell successfully, False otherwise.
        """

        logger.info("Beginning agent training")
        self.agent.environment = self.world
        self.agent.run_value_iteration(value_search_iterations, threshold, gamma)
        self.agent.run_policy_iteration(policy_search_iterations, gamma)
        self.agent.plot_q_function()

        logger.info("Solving game")
        player_positions, reached_goal = self.agent.solve()

        self.world.print(player_positions[1:-1])

        if reached_goal:
            logger.info(f"Agent solved the problem in {len(player_positions) - 1} moves!")
        else:
            logger.info(f"Agent could not solve the problem!")

        return player_positions, reached_goal


from django.contrib import admin
from django.contrib.contenttypes import generic
from django import forms
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext
from aircraftlogger.models import *
from soaring_club.conf.settings import *

class InstallationInline(admin.TabularInline):
    model = Installation
    extra = 1

class InstallationAdmin(admin.ModelAdmin):
    list_display = ('plane', 'flarm', 'flarmid', 'date_added', 'date_removed')
admin.site.register(Installation, InstallationAdmin)

class OwnershipInline(admin.TabularInline):
    model = Ownership
    extra = 1

class OwnershipAdmin(admin.ModelAdmin):
    list_display = ('plane', 'owner', 'date_purchased', 'date_sold')
admin.site.register(Ownership, OwnershipAdmin)

class MemberTypeInline(generic.GenericTabularInline):
    model = MemberType
    extra = 2
#admin.site.register(MemberType)

class MemberAdmin(admin.ModelAdmin):
    prepopulated_fields = {'sort_name' : ('last_name', 'first_name')}
    list_display = ('last_name', 'first_name', 'user', 'types', 'email', 'notes')
    inlines = [OwnershipInline,MemberTypeInline,]
    search_fields = ('last_name', 'first_name', 'type__type')

admin.site.register(Member, MemberAdmin)

class PlaneAdmin(admin.ModelAdmin):
    list_display = ('type', 'regmark', 'manufacturer', 'model', 'owners_str')
    list_display_links = ('regmark', )
    list_filter = ('type',)
    inlines = [InstallationInline, OwnershipInline]
admin.site.register(Plane, PlaneAdmin)

class FlarmAdmin(admin.ModelAdmin):
    inlines = [InstallationInline,]
    extra = 1
admin.site.register(Flarm, FlarmAdmin)

class FlightBillInline(admin.TabularInline):
    model = FlightBill
    verbose_name = verbose_name_plural = 'Flight bill'
    fk_name = 'flight'
    extra = 1

class GliderFlightInline(admin.TabularInline):
    model = FlightBill
    verbose_name = verbose_name_plural = 'Glider flight'
    fk_name = 'tow_flight'
    extra = 1

class FlightAdminForm(forms.ModelForm):
    takeoff_field = forms.CharField(max_length=20, initial=HOME_AIRPORT, required=False, label=_('Takeoff field'))
    landing_field = forms.CharField(max_length=20, initial=HOME_AIRPORT, required=False, label=_('Landing field'))
    class Meta:
        model = Flight

class FlightAdmin(admin.ModelAdmin):
    list_display = ('id', 'date', 'plane', 'pilot', 'porpouse','takeoff', 'landing', 'landing_field', 'cost' )
    #list_editable = ('date', 'takeoff',  'landing', 'pilot', 'plane',)
    date_hierarchy = 'date'
    list_filter = ( 'porpouse', 'plane', 'pilot' )
    search_fields = ('id', 'pilot__sort_name', 'plane__regmark', 'plane__model', 'plane__type', 'porpouse', )
    inlines = [FlightBillInline, ]
    save_as = True
    save_on_top = True
    form = FlightAdminForm

admin.site.register(Flight, FlightAdmin)

class FlightBillAdmin(admin.ModelAdmin):
    list_display = ('__unicode__', 'flight', 'tow_flight', 'payer', 'cost', 'cost_class',)
    list_filter = ('cost_class', 'payer',)
    search_fields = ('payer__sort_name', 'flight__id')

admin.site.register(FlightBill, FlightBillAdmin)

admin.site.register(MemberCredit)

class ReceiptDetailGenericInline(admin.TabularInline):
    model = ReceiptDetailGeneric
    fields = ('description', 'quantity', 'amount', 'total', 'memo')
    formfield_overrides = {
        models.TextField: {'widget': forms.Textarea(attrs={'class':'note', 'rows':'1', 'cols':'30'})}
    }
    verbose_name  = 'Item'
    verbose_name_plural = 'Items'
    extra = 3
#admin.site.register(ReceiptDetailGeneric)

# class TowDetailInline(admin.TabularInline):
#     model = TowDetail
#     fields = ('cents', 'cent_cost', 'total', 'memo')
#     formfield_overrides = {
#         models.TextField: {'widget': forms.Textarea(attrs={'class':'note', 'rows':'1', 'cols':'30'})}
#     }
#     verbose_name  = 'Tow credit'
#     verbose_name_plural = 'Tow credits'
#     extra = 1
#     max_num = 1
#admin.site.register(TowDetail)

class ReceiptDetailFlightInline(admin.TabularInline):
    model = ReceiptDetailFlight
    fields = ('credit_class', 'credit', 'price_credit', 'total', 'memo')
    formfield_overrides = {
        models.TextField: {'widget': forms.Textarea(attrs={'class':'note', 'rows':'1', 'cols':'30'})}
    }
    verbose_name  = 'Flights credit'
    verbose_name_plural = 'Flights credits'
    extra = 3
    max_num = 3

# class MotorGliderDetailInline(admin.TabularInline):
#     model = MotorGliderDetail
#     formfield_overrides = {
#         models.TextField: {'widget': forms.Textarea(attrs={'class':'note', 'rows':'1', 'cols':'30'})}
#     }
#     fields = ('cents', 'cent_cost', 'total', 'memo')
#     verbose_name  = 'Motor glider credit'
#     verbose_name_plural = 'Motor glider credits'
#     extra = 1
#     max_num = 1
#admin.site.register(MotorGliderDetail)

class ReceiptAdmin(admin.ModelAdmin):
    inlines = [ReceiptDetailGenericInline, ReceiptDetailFlightInline]
    date_hierarchy = 'date'
    list_filter = ('description',)
    search_fields = ('reference', 'payer__sort_name', 'description')
    exclude = ('created','created_by', 'updated', 'updated_by')
    list_display = ('reference', 'date', 'payer', 'tow_cents', 'tmg_cents', 'total', 'description', 'created_by', 'created')
    #list_editable = ('reference', 'payer', 'note')
    formfield_overrides = {
        models.TextField: {'widget': forms.Textarea(attrs={'class':'note', 'rows':'1', 'cols':'50'})}
    }

    def save_model(self, request, obj, form, change):
        if not change:
            obj.created_by = request.user
        else:
            obj.updated_by = request.user
        if form.is_valid():
            description = form.cleaned_data['description']
            if not description:
                if form.data:
                    cents = ugettext('Cents')
                    description = ''
                    for k,v in form.data.iteritems():
                        if 'description' in k and v:
                            description += ', '+v
                        elif 'receiptdetailflight' in k and 'credit_class' in k and v:
                            if v == 'towing':
                                cents = ugettext('Tow cents')
                            elif v == 'tmg':
                                cents = ugettext('TMG cents')
                            elif v == 'tourist':
                                cents = ugettext('Tourist cents')
                            elif v == 'promo':
                                cents = ugettext('Promotional flight')
                            description += ', '+cents
                    if description:
                        description = description[2:]
            obj.description = description
        obj.save()

admin.site.register(Receipt, ReceiptAdmin)

class TowerLogAdmin(admin.ModelAdmin):
    list_display = ('log_file', 'created')
admin.site.register(TowerLog, TowerLogAdmin)


# -*- coding: utf-8 -*-
# Project  : TensorFlowProjects
# File     : ep2.py
# Author   : guile
# Version  : v1.0
# Email    : lavender.lhy@gmail.com
# Date     : 2020-11-10 15:29
# Remarks  : 使用 tf.data 加载文本数据
# Tensorflow and keras
import tensorflow as tf
import tensorflow_datasets as tfds
import os

# 需要设置gpu的memory，，，否则报错
gpus = tf.config.list_physical_devices('GPU')
print(gpus)
tf.config.experimental.set_memory_growth(gpus[0], True)

DIRECTORY_URL = 'https://storage.googleapis.com/download.tensorflow.org/data/illiad/'
FILE_NAMES = ['cowper.txt', 'derby.txt', 'butler.txt']

for name in FILE_NAMES:
    text_dir = tf.keras.utils.get_file(name, origin=DIRECTORY_URL + name)

parent_dir = "/home/guile/.keras/datasets"

def labeler(example, index):
    return example, tf.cast(index, tf.int64)

labeled_data_sets = []

for i, file_name in enumerate(FILE_NAMES):
    line_dataset = tf.data.TextLineDataset(os.path.join(parent_dir, file_name))
    labeled_dataset = line_dataset.map(lambda ex: labeler(ex, i))
    labeled_data_sets.append(labeled_dataset)

BUFFER_SIZE = 50000
BATCH_SIZE = 65
TAKE_SIZE = 5000

all_labeled_data = labeled_data_sets[0]
for label_set in labeled_data_sets[1:]:
    all_labeled_data = all_labeled_data.concatenate(label_set)

all_labeled_data = all_labeled_data.shuffle(BUFFER_SIZE, reshuffle_each_iteration=False)

tokenizer = tfds.features.text.Tokenizer()

vocabulary_set = set()
for text_tensor, _ in all_labeled_data:
    some_tokens = tokenizer.tokenize(text_tensor.numpy())
    vocabulary_set.update(some_tokens)

vocab_size = len(vocabulary_set)

encoder = tfds.features.text.TokenTextEncoder(vocabulary_set)
example_set = next(iter(all_labeled_data))[0].numpy()

encoded_example = encoder.encode(example_set)

def encode(tt, label):
    encoded_text = encoder.encode(tt.numpy())
    return encoded_text, label

def encode_map_fn(text, label):
    encoded_text, label = tf.py_function(encode, inp=[text, label], Tout=(tf.int64, tf.int64))
    encoded_text.set_shape([None])
    label.set_shape([])
    return encoded_text, label

all_encoded_data = all_labeled_data.map(encode_map_fn)

train_data = all_encoded_data.skip(TAKE_SIZE).shuffle(BUFFER_SIZE)
train_data = train_data.padded_batch(BATCH_SIZE)

test_data = all_encoded_data.take(TAKE_SIZE)
test_data = test_data.padded_batch(BATCH_SIZE)

sample_text, sample_label = next(iter(test_data))
print(sample_text[0])
print()
print(sample_label[0])

vocab_size += 1

model = tf.keras.Sequential()
model.add(tf.keras.layers.Embedding(vocab_size, 64))
model.add(tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(64)))

for units in [64, 64]:
    model.add(tf.keras.layers.Dense(units, activation="relu"))

model.add(tf.keras.layers.Dense(3, activation="softmax"))

model.compile(optimizer="adam", loss="sparse_categorical_crossentropy", metrics=["accuracy"])

model.fit(train_data, epochs=3, validation_data=test_data)

eval_loss, eval_acc = model.evaluate(test_data)

print()
print("Eval Loss: {}, Eval Acc: {}".format(eval_loss, eval_acc))


from django.db import models
from django.contrib.auth import get_user_model
from cloudinary.models import CloudinaryField
from tinymce.models import HTMLField
User = get_user_model()

# Create your models here.
class AllPlantTable(models.Model):
    """All Plants."""

    TOXICITY_LEVEL = [
        ("LOW", "LOW"),
        ("MEDIUM", "MEDIUM"),
        ("HIGH", "HIGH"),
    ]
    TYPE_BY_SIZE = [
        ("Herbs", "Herbs"),
        ("Shrubs", "Shrubs"),
        ("Trees", "Trees"),
        ("Herbaceous", "Herbaceous"),
        ("Creepers", "Creepers"),
        ("Climbers", "Climbers"),
    ]
    LIFE_CYCLE = [
        ("Perennial", "Perennial"),
        ("Annual", "Annual"),
        ("Bi-Annual", "Bi-Annual"),
    ]
    name = models.CharField(max_length=500)
    botanical_name = models.CharField(max_length=500, blank=True, null=True)
    type_by_size = models.CharField(max_length=500, choices=TYPE_BY_SIZE, blank=True, null=True)
    type_by_life_cycle = models.CharField(max_length=500, choices=LIFE_CYCLE, blank=True, null=True)
    plant_care = models.TextField(blank=True, null=True)
    toxicity = models.TextField(blank=True, null=True)
    toxicity_level = models.CharField(max_length=500,choices=TOXICITY_LEVEL, blank=True, null=True)
    plan_removes_toxins = models.TextField(blank=True, null=True)
    description = models.TextField(blank=True, null=True)
    image = CloudinaryField('image', default="image/upload/v1677969748/sample.jpg")
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        """Return name."""
        return self.name

class Blog(models.Model):
    """All Plants."""

    blog_title = models.CharField(max_length=500, blank=True, null=True)
    blog_user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True)
    blog_body = HTMLField()
    created_at = models.DateTimeField(auto_now_add=True)
    count = models.IntegerField(default=0)
    image = CloudinaryField('image', default="image/upload/v1677969748/sample.jpg")
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ['created_at']

    def __str__(self):
        return f'Blog {self.blog_title} by {self.blog_user}'

class Forum(models.Model):

    forum_title = models.CharField(max_length=500, blank=True, null=True)
    forum_user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True)
    forum_body = models.TextField()
    active = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ['created_at']

class Comment(models.Model):
    blog = models.ForeignKey(Blog,on_delete=models.CASCADE,related_name='blog_comments', blank=True, null=True)
    forum = models.ForeignKey(Forum,on_delete=models.CASCADE,related_name='forum_comments', blank=True, null=True)
    user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True)
    body = models.TextField()
    active = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ['created_at']

    def __str__(self):
        return 'Comment {} by {}'.format(self.body, self.user.username)


import gdrive
import os
import shutil
import discord
from pathlib import Path
from discord.ext import commands
from dotenv import load_dotenv
import asyncio

load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')
# The token key is load from the .env file

FFMPEG_PATH = "/usr/bin/ffmpeg"
PATH = Path("/home/aurnytoraink/Projets/Code/Bot/Diskcord/") # <= Set yout path

# discord.opus.load_opus()
voicechannel = []
bot = commands.Bot(command_prefix='$')

# Loads modules
service = gdrive.login()

# Reset cache music
if os.path.exists(PATH / 'musics'):
    shutil.rmtree(PATH / "musics")
os.makedirs(PATH / "musics")

@bot.event
async def on_ready():
    print(f'{bot.user} has connected to Discord!')

@bot.command(name="join", help="Invite le bot dans un salon vocal")
async def join(ctx):
    user = ctx.message.author
    if user.voice is not None:
        place = len(voicechannel)
        voicechannel.append([await user.voice.channel.connect(timeout=100),[],0])
        voicechannel[place][0].play((discord.FFmpegPCMAudio(PATH / "connected_sound.opus", executable=FFMPEG_PATH)))

#TODO Corriger pour que le titre de la musique s'afficher Ã  chaque fois qu'il passe Ã  la suivante
#TODO Ne laisser passer que les liens Google, Nextcloud
@bot.command(name="play", help="Lien de la (ou les) musique(s) Ã  jouer")
async def play(ctx, *args):
    user = ctx.message.author
    def check_playlist(vc):
        if vc[1] != [] and vc[2] < len(vc[1]) - 1:
            vc[2] += 1
            currentMusic = vc[1][vc[2]][0]
            gdrive.downloadMedia(service, currentMusic, str(id))
            # task = asyncio.ensure_future(ctx.send("**Je chanteðŸŽ¶: **" + currentMusicName + "\n**Queue:** {}/{}".format((vc[2]+1),len(vc[1]))))
            # print(task)
            vc[0].play(discord.FFmpegPCMAudio(PATH / ("musics/"+str(id)+'/'+currentMusic), executable=FFMPEG_PATH), after=lambda e: check_playlist(vc))
            return

    try:
        voicechannel.append([await user.voice.channel.connect(timeout=100),[],0])
    except AttributeError:
        await ctx.send("**Hmmm... Vous n'Ãªtes pas connectÃ© Ã  un salon vocal !**")
    except discord.errors.ClientException:
        pass
    for vc in voicechannel:
        if vc[0].channel == user.voice.channel:
            if len(args) == 0:
                if vc[0].is_paused() or vc[0].is_playing():
                    if vc[0].is_paused():
                        vc[0].resume()
                        return
                    else:
                        await ctx.send("Ã‚llo ? Vous m'entendez ?\nLa musique est dÃ©jÃ  en cours de lecture !")
                        return
                else:
                    await ctx.send("-ã�ˆï¼Ÿ\nJe dois jouer quelle musique ?")
                    return
            elif args[0] == "random":
                queuedlist = gdrive.getMedias(service, args[1], True)
            elif args[0] == "add":
                if args[1] == "random":
                    queuedlist = gdrive.getMedias(service, args[2], True)
                else:
                    queuedlist = gdrive.getMedias(service, args[1])
                if queuedlist is None:
                    await ctx.send("**Aucune musique trouvÃ©e !**")
                    return
                vc[1] += queuedlist
                await ctx.send("**Youpi!** J'ai ajoutÃ© votre dossier Ã  la queue.")
                return
            else:
                queuedlist = gdrive.getMedias(service, args[0])
            if queuedlist is None:
                await ctx.send("Aucune musique trouvÃ©e !")
                return
            if vc[0].is_playing:
                vc[1] = []
                vc[2] = 0
                vc[0].stop()
            await asyncio.sleep(1)
            await ctx.send("**Chargement...**")
            vc[1] = queuedlist
            id = vc[0].channel.id
            currentMusic = vc[1][vc[2]][0]
            currentMusicName = vc[1][vc[2]][1]
            gdrive.downloadMedia(service, currentMusic, str(id))
            await ctx.send("**Je chanteðŸŽ¶: **" + currentMusicName + "\n**Queue:** {}/{}".format((vc[2]+1),len(vc[1])))
            vc[0].play(discord.FFmpegPCMAudio(PATH / ("musics/"+str(id)+'/'+currentMusic), executable=FFMPEG_PATH), after=lambda e: check_playlist(vc))
        else:
            place = len(voicechannel)
            voicechannel.append([await user.voice.channel.connect(timeout=100),[],0])
            voicechannel[place][0].play((discord.FFmpegPCMAudio(PATH / "connected_sound.opus", executable=FFMPEG_PATH)))

@bot.command(name="np", aliases=["display","current"], help="Affiche la musique en cours")
async def np(ctx):
    user=ctx.message.author
    for vc in voicechannel:
        if vc[0].channel == user.voice.channel:
            if vc[0].is_playing():
                await ctx.send("**Je chanteðŸŽ¶: **" + vc[1][vc[2]][1] + "\n**Queue:** {}/{}".format((vc[2]+1),len(vc[1])))
            else:
                await ctx.send("**AÃ¯e AÃ¯e AÃ¯e !** Je joue pas de musique actuellement")

@bot.command(name="add", help="Rajoute la (ou les) musique(s) Ã  la queue actuelle")
async def add(ctx, *args):
    user=ctx.message.author
    for vc in voicechannel:
        if vc[0].channel == user.voice.channel:
            if len(args) == 0:
                await ctx.send("-ã�ˆï¼Ÿ\nJe dois rajouter quelle musique ?")
                return
            elif args[0] == "random":
                queuedlist = gdrive.getMedias(service, args[1], True)
            else:
                queuedlist = gdrive.getMedias(service, args[0])
            if queuedlist is None:
                await ctx.send("**Aucune musique trouvÃ©e !**")
                return
            vc[1] += queuedlist
            await ctx.send("**Youpi!** J'ai ajoutÃ© votre dossier Ã  la queue.")

@bot.command(name="pause", help='Met en pause la musique')
async def pause(ctx):
    user=ctx.message.author
    for vc in voicechannel:
        if vc[0].channel == user.voice.channel:
            if vc[0].is_paused():
                vc[0].resume()
            else:
                vc[0].pause()
        else:
            await ctx.send("Vous n'Ãªtes pas dans un salon vocal !")

@bot.command(name="resume", help='Fonction inverse de $pause') #TODO Prevent user if there is not music playing
async def resume(ctx):
    user=ctx.message.author
    for vc in voicechannel:
        if vc[0].channel == user.voice.channel:
            if vc[0].is_paused():
                vc[0].resume()
            else:
                await ctx.send("Ã‚llo ? Vous m'entendez ?\nLa musique est dÃ©jÃ  en cours de lecture !")
        else:
            await ctx.send("Vous n'Ãªtes pas dans un salon vocal !")

@bot.command(name="stop", help="ArrÃªte la musique et efface la liste d'attente")
async def stop(ctx):
    user=ctx.message.author
    for vc in voicechannel:
        if vc[0].channel == user.voice.channel:
            vc[1] = []
            vc[2] = 0
            vc[0].stop()
        else:
            await ctx.send("Vous n'Ãªtes pas dans un salon vocal !")

@bot.command(name="next", aliases=['skip','s'], help="Passe Ã  la musique suivante\nPS: Vous pouvez passer plusieurs musiques en indiquant le nombre de musique Ã  sauter")
async def next(ctx, *arg):
    user=ctx.message.author
    for vc in voicechannel:
        if vc[0].channel == user.voice.channel:
            if len(arg) != 0:
                if len(vc[1]) > vc[2]+int(arg[0]):
                    vc[2] += int(arg[0]) - 1
                    vc[0].stop()
                    currentMusicName = vc[1][vc[2]+1][1]
                    await ctx.send("**Je chanteðŸŽ¶: **" + currentMusicName + "\n**Queue:** {}/{}".format((vc[2]+2),len(vc[1])))
                else:
                    await ctx.send("**AÃ¯e aÃ¯e aÃ¯e !**\n`Out of Range: Le nombre entrÃ© dÃ©passe la liste`")
            else:
                if len(vc[1]) - 1 != vc[2]:
                    vc[0].stop()
                    currentMusicName = vc[1][vc[2]+1][1]
                    await ctx.send("**Je chanteðŸŽ¶: **" + currentMusicName + "\n**Queue:** {}/{}".format((vc[2]+2),len(vc[1])))
                else:
                    await ctx.send("**Fin de la queue atteinte**")

@bot.command(name="back", aliases=['b'], help="Reviens Ã  la musique prÃ©cÃ©dente\nPS: Vous pouvez revenir plusieurs fois en indiquant le nombre de musique Ã  sauter")
async def back(ctx, *arg):
    user=ctx.message.author
    for vc in voicechannel:
        if vc[0].channel == user.voice.channel:
            if len(arg) != 0:
                if vc[2] - int(arg[0]) >= 0 and vc[2] > 0:
                    vc[2] -= int(arg[0]) + 1
                    vc[0].stop()
                    currentMusicName = vc[1][vc[2]+1][1]
                    await ctx.send("**Je chanteðŸŽ¶: **" + currentMusicName + "\n**Queue:** {}/{}".format((vc[2]+2),len(vc[1])))
                else:
                    await ctx.send("**AÃ¯e aÃ¯e aÃ¯e !**\nQueuedlist: Out of Range > `Le nombre entrÃ© dÃ©passe la liste`")
            else:
                if vc[2] > 0:
                    vc[2] -= 2
                    vc[0].stop()
                    currentMusicName = vc[1][vc[2]+1][1]
                    await ctx.send("**Je chanteðŸŽ¶: **" + currentMusicName + "\n**Queue:** {}/{}".format((vc[2]+2),len(vc[1])))
                else:
                    await ctx.send("**Vous avez atteint le dÃ©but de la liste !**")

@bot.command(name="leave", help="Permet au bot de se dÃ©gourdir les jambes (ou plutÃ´t la bande passante")
async def leave(ctx):
    user=ctx.message.author
    for vc in voicechannel:
        if vc[0].channel == user.voice.channel:
            vc[1] = []
            vc[2] = 0
            vc[0].stop()
            vc[0].play(discord.FFmpegPCMAudio(PATH / "leave_sound.opus", executable=FFMPEG_PATH))
            await asyncio.sleep(1.5)
            await vc[0].disconnect()
            await ctx.send("Bis bald! :wave:")
            voicechannel.remove(vc)
            shutil.rmtree(PATH / ('musics/'+str(vc[0].channel.id)))
        else:
            await ctx.send("Vous n'Ãªtes pas dans un salon vocal !")

bot.run(TOKEN)


import torch
import torch.nn as nn
import torch.nn.functional as F

import math
from transformers import BertPreTrainedModel, BertConfig, BertModel

# ------------------------------------------------
# --------- attention sum feature ----------------
# ------------------------------------------------

class AttSum(nn.Module):
    def __init__(self, C):
        super(AttSum, self).__init__()

        self.att = nn.Sequential(
            nn.Linear(C.HIDDEN_SIZE, C.HIDDEN_SIZE),
            nn.ReLU(inplace=True),
            nn.Dropout(C.DROPOUT_R),
            nn.Linear(C.HIDDEN_SIZE, 1)
        )
        self.fc = nn.Linear(C.HIDDEN_SIZE, C.HIDDEN_SIZE)

    def forward(self, x):
        att_w = self.att(x)
        att_w = F.softmax(att_w, dim=1)
        x_att = torch.sum(att_w * x, dim=1)
        x_att = self.fc(x_att)

        return x_att

# ----------------------------------------------
# ---------------- MLP -------------------------
# ----------------------------------------------

class MlpModel(nn.Module):
    def __init__(self, C, answer_size):
        super(MlpModel, self).__init__()

        self.att_img = AttSum(C)

        self.proj = nn.Sequential(
            GeLU(),
            nn.Linear(C.HIDDEN_SIZE, answer_size)
        )

    def forward(self, v, q):

        q = q[:,0,:]
        v = self.att_img(v)

        pred = v + q
        pred = self.proj(pred)

        return pred

class GeLU(nn.Module):
    """Implementation of the gelu activation function.
        For information: OpenAI GPT's gelu is slightly different (and gives slightly different results):
        0.5 * x * (1 + torch.tanh(math.sqrt(2 / math.pi) * (x + 0.044715 * torch.pow(x, 3))))
        Also see https://arxiv.org/abs/1606.08415
    """
    def __init__(self):
        super().__init__()

    def forward(self, x):
        return x * 0.5 * (1.0 + torch.erf(x / math.sqrt(2.0)))