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

"""
flask_hal.document
==================

Module for constructing ``HAL`` documents.

Example:
    >>> from flask_hal.document import Document
    >>> d = Document()
    >>> d.to_dict()
"""

# Standard Libs
import json

# First Party Libs
from flask_hal import link

class BaseDocument(object):
    """Constructs a ``HAL`` document.
    """

    def __init__(self, data=None, links=None, embedded=None):
        """Base ``HAL`` Document. If no arguments are provided a minimal viable
        ``HAL`` Document is created.

        Keyword Args:
            data (dict): Data for the document
            links (flask_hal.link.Collection): A collection of ``HAL`` links
            embedded: TBC

        Raises:
            TypeError: If ``links`` is not a :class:`flask_hal.link.Collection`
        """

        self.data = data
        self.embedded = embedded or {}
        self.links = links or link.Collection()

    @property
    def links(self):
        return self._links

    @links.setter
    def links(self, value):
        if not isinstance(value, link.Collection):
            if isinstance(value, (list, set, tuple)):
                value = link.Collection(*value)
            else:
                raise TypeError('links must be a {0} or {1} instance'.format(
                                link.Collection, list))
        self._links = value

    @property
    def embedded(self):
        return self._embedded

    @embedded.setter
    def embedded(self, value):
        if not isinstance(value, dict):
            raise TypeError('embedded must be a {0} instance'.format(dict))
        self._embedded = value

    def to_dict(self):
        """Converts the ``Document`` instance into an appropriate data
        structure for HAL formatted documents.

        Returns:
            dict: The ``HAL`` document data structure
        """

        document = {}

        # Add Data to the Document
        if isinstance(self.data, dict):
            document.update(self.data)

        # Add Links
        if self.links:
            document.update(self.links.to_dict())

        # Add Embedded: Embedded API TBC
        if self.embedded:
            document.update({
                '_embedded': dict(
                    (n, v.to_dict()) for n, v in self.embedded.items()
                )
            })

        return document

    def to_json(self):
        """Converts :class:`.Document` to a ``JSON`` data structure.

        Returns:
            str: ``JSON`` document
        """

        return json.dumps(self.to_dict())

class Document(BaseDocument):
    """Constructs a ``HAL`` document.
    """

    def __init__(self, data=None, links=None, embedded=None, external_self=False):
        """Initialises a new ``HAL`` Document instance. If no arguments are
        provided a minimal viable ``HAL`` Document is created.

        Keyword Args:
            data (dict): Data for the document
            links (flask_hal.link.Collection): A collection of ``HAL`` links
            embedded: TBC
            external_self: use a fully-qualified link for self

        Raises:
            TypeError: If ``links`` is not a :class:`flask_hal.link.Collection`
        """
        super(Document, self).__init__(data, links, embedded)
        self.links.append(link.Self(external=external_self))

class Embedded(BaseDocument):
    """Constructs a ``HAL`` embedded.

    Example:
        >>> document = Document(
        >>>     embedded={
        >>>         'orders': Embedded(
        >>>             embedded={'details': Embedded(
        >>>                 data={'details': {}}
        >>>             )},
        >>>             links=link.Collection(
        >>>                 link.Link('foo', 'www.foo.com'),
        >>>                 link.Link('boo', 'www.boo.com')
        >>>             ),
        >>>             data={'total': 30},
        >>>         )
        >>>     },
        >>>     data={'currentlyProcessing': 14}
        >>> )
        >>> document.to_json()
        ... {
                "_links": {
                    "self": {"href": "/entity/231"}
                },
                "_embedded": {
                    "orders": {
                        "_embedded": {
                            "details": {"details": {}}
                        },
                        "total": 30,
                        "_links": {
                            "foo": {"href": "www.foo.com"},
                            "boo": {"href": "www.boo.com"}
                        }
                    }
                },
                "currentlyProcessing": 14
            }
    """

    def to_dict(self):
        """Converts the ``Document`` instance into an appropriate data
        structure for HAL formatted documents.

        Returns:
            dict: The ``HAL`` document data structure
        """
        if isinstance(self.data, (list, tuple, set)):
            data = []

            for item in self.data:
                if isinstance(item, BaseDocument):
                    data.append(item.to_dict())
                else:
                    data.append(item)
            return data

        return super(Embedded, self).to_dict()


import warnings
import os
from .config import BasicConfig
class DefaultConfig(BasicConfig):
    model_root = '/data/fyl/models_pytorch'
    model = 'EnsembleNet_Paramwise'
    model_setup = {'pretrained': True}
    connection_mode = 'singleline'
    env = model + '_oa_lidc3s_classification_pretrained_adam_rfold1|10'
    Devices_ID = '6'

    dataset = 'Pathology_Slicewise_Dataset'
    label_mode = 'binclass'
    patch_mode = 'oa'
    root_dir = '/data/fyl/data_samples/lidc_3slices'
    #root_dir = model_root + "/BasicNet_lidc_classification_9/filelist_train_fold[0, 1, 2, 3, 4].log"
    patientlist = '/data/fyl/models_pytorch/BaseweightSharedBNNet_classification_pretrained_adam_rfold8|10/patientlist.log'
    filelist_shuffle = False
    num_cross_folds = 10
    val_fold = 1
    test_fold = -1
    remove_uncertain = True
    filelists = dict(
        #train = model_root + '/BasicNet_classification_lidc_test_rfold1/filelist_train_fold[1, 2, 3, 4].log',
        #val = model_root + '/BasicNet_classification_lidc_test_rfold1/filelist_val_fold0.log',
        test = model_root + '/EnsembleNet_Paramwise_oa_lidc3s_classification_pretrained_adam_rfold1|10/filelist_val_fold0.log'
    )

    #load_model_path = None
    #load_model_path = model_root + "/ParamResNet50_lidc_classification_pretrained_rfold1|10/ParamResNet50_lidc_classification_pretrained_rfold1|10_epoch22"
    #load_model_path = model_root + '/BasicNet_lidc_classification_9/BasicNet_lidc_classification_9_epoch500'
    #load_model_path = model_root + '/BasicNet_classification_lidc_transfer_rfold5/BasicNet_classification_lidc_transfer_rfold5_epoch1330'
    #load_model_path = model_root + '/EnsembleNet_Paramwise_lidc_classification_adam_rfold1|10/EnsembleNet_Paramwise_lidc_classification_adam_rfold1|10_epoch10'
    #load_model_path = model_root + "/DenseNet_Iterative_trand_fold1/DenseNet_Iterative_trand_fold1_epoch101"
    #load_model_path = model_root + "/ParamDenseCropNet_classification_fold4|5/ParamDenseCropNet_classification_fold4|5_epoch146"
    inference_setup = {'model_name': 'EnsembleNet_Paramwise_oa_lidc3s_classification_pretrained_adam_rfold1|10', 'epochs': [2, 3, 18, 19, 27, 35, 46]}
    use_gpu = True

    input_size = 200
    batch_size = 10
    print_freq = 50
    save_freq = 1

    data_preprocess = dict(
        translation_num = 1,
        translation_range = (-10, 10),
        rotation_num = 3,
        flip_num = 2,
	shear_num = 0,
        noise_range = (0, 0)
    )

    #drop_rate = 0
    weight_decay = 0
    loss_exp = 0
    balancing = (0.5, 0.5)

    max_epoch = 50
    lr = 5e-6

'''
further improvements:
1) randomly augment differently among 3 input channels;
2) add drop out, weight decay, or balancing factor;
3) further read implementation details from the original paper.
'''


from flask import request, make_response, jsonify
from werkzeug.exceptions import NotFound

from datetime import datetime
from ....data import Data

class ProductModel():
    """This class encapsulates the functions of the product model"""

    def __init__(self, name, stock, min_q, category, product_id=0):
        """initialize the question model"""
        self.name = name
        self.stock = stock
        self.min_q = min_q
        self.date_created = datetime.now()
        self.category = category
        self.product_id = product_id

    def save_product(self):
        product_id = len(Data.products) + 1
        product = {
            "product_id": product_id,
            "name": self.name,
            "min_q": self.min_q,
            "category": self.category,
            "stock": self.stock,
            "date_created": self.date_created

        }
        #save the product to the products list
        Data.products.append(product)

    @staticmethod
    def edit_product(product_id, name, stock, category, min_q):
        """method to Edit Product details"""

        update_prd = [
            prod for prod in Data.products if prod['product_id'] == product_id]

        if update_prd:
            print(update_prd)
            update_prd[0]['name'] = name
            update_prd[0]['stock'] = stock
            update_prd[0]['category'] = category
            update_prd[0]['min_q'] = min_q
            return update_prd
        raise NotFound('Product Not Found')

    @staticmethod
    def delete_product(product_id):
        """Class method to delete products from inventory"""

        deleted_prd = [
            prod for prod in Data.products if prod['product_id'] == product_id]
        if deleted_prd:
            Data.products.remove(deleted_prd[0])
            return deleted_prd
        raise NotFound('Product Not Found')


"""
Make best efforts to decide if a task needs to run again, or if it is pointless
because the source code hasn't changed.

Sometimes task depends on the world, not the source code

Strategies
- folder or source tree changed
- file changed
- list of files changed

Is it worse is lint counting. Move to different module?
"""
import functools
import hashlib
import os
import shutil
import sys
import time
from typing import Any, Callable, Optional, TypeVar, cast

from checksumdir import dirhash

from navio_tasks import settings as settings
from navio_tasks.settings import PROJECT_NAME, SRC
from navio_tasks.utils import inform

# pylint: disable=invalid-name
FuncType = Callable[..., Any]
# pylint: disable=invalid-name
F = TypeVar("F", bound=FuncType)
CURRENT_HASH = None

# bash to find what has change recently
# find src/ -type f -print0 | xargs -0 stat -f "%m %N" | sort -rn | head -10 |
# cut -f2- -d" "

class BuildState:
    """
    Try not to re-do what doesn't need to be redone
    """

    def __init__(self, what: str, directory: str) -> None:
        """
        Set initial state
        """
        self.what = what
        self.directory = directory
        if not os.path.exists(f"{settings.CONFIG_FOLDER}/.build_state"):
            os.makedirs(f"{settings.CONFIG_FOLDER}/.build_state")
        self.state_file_name = (
            f"{settings.CONFIG_FOLDER}/.build_state/last_change_{what}.txt"
        )

    def oh_never_mind(self) -> None:
        """
        If a task fails, we don't care if it didn't change since last, re-run,
        """
        # noinspection PyBroadException
        # pylint: disable=bare-except
        try:
            os.remove(self.state_file_name)
        except:  # noqa: B001
            pass

    def has_source_code_tree_changed(self) -> bool:
        """
        If a task succeeds & is re-run and didn't change, we might not
        want to re-run it if it depends *only* on source code
        """
        # pylint: disable=global-statement
        global CURRENT_HASH
        directory = self.directory

        # if CURRENT_HASH is None:
        # inform("hashing " + directory)
        # inform(os.listdir(directory))
        CURRENT_HASH = dirhash(
            directory,
            "md5",
            ignore_hidden=True,
            # changing these exclusions can cause dirhas to skip EVERYTHING
            # excluded_files=[".coverage", "lint.txt"],
            excluded_extensions=[".pyc"],
        )

        inform("Searching " + self.state_file_name)
        if os.path.isfile(self.state_file_name):
            with open(self.state_file_name, "r+") as file:
                last_hash = file.read()
                if last_hash != CURRENT_HASH:
                    file.seek(0)
                    file.write(CURRENT_HASH)
                    file.truncate()
                    return True
                return False

        # no previous file, by definition not the same.
        with open(self.state_file_name, "w") as file:
            file.write(CURRENT_HASH)
            return True

def oh_never_mind(what: str) -> None:
    """
    If task fails, remove file that says it was recently run.
    Needs to be like this because tasks can change code (and change the hash)
    """
    state = BuildState(what, PROJECT_NAME)
    state.oh_never_mind()

def has_source_code_tree_changed(
    task_name: str, expect_file: Optional[str] = None
) -> bool:
    """
    Hash source code tree to know if it has changed

    Also check if an expected output file exists or not.
    """
    if expect_file:
        if os.path.isdir(expect_file) and not os.listdir(expect_file):
            os.path.dirname(expect_file)
            # output folder empty
            return True
        if not os.path.isfile(expect_file):
            # output file gone
            return True
    state = BuildState(task_name, os.path.join(SRC, PROJECT_NAME))
    return state.has_source_code_tree_changed()

def skip_if_no_change(name: str, expect_files: Optional[str] = None) -> F:
    """
    Don't run decorated task if nothing in the source has changed.
    """

    # https://stackoverflow.com/questions/5929107/decorators-with-parameters
    def real_decorator(func: F) -> F:
        """Wrapper"""

        @functools.wraps(func)
        def wrapper(*args: Any, **kwargs: Any) -> Callable:
            """Wrapper"""
            if not has_source_code_tree_changed(name, expect_files):
                inform("Nothing changed, won't re-" + name)
                return lambda x: None
            try:
                return func(*args, **kwargs)
            except:  # noqa: B001
                oh_never_mind(name)
                raise

        return cast(F, wrapper)

    return cast(F, real_decorator)

def hash_it(path: str) -> str:
    """
    Hash a single file. Return constant if it doesn't exist.
    """
    if not os.path.exists(path):
        return "DOESNOTEXIST"
    with open(path, "rb") as file_handle:
        return hashlib.sha256(file_handle.read()).hexdigest()

def skip_if_this_file_does_not_change(name: str, file: str) -> F:
    """
    Skip decorated task if this referenced file didn't change. Useful
    if a task depends on a single file and not (potentially) any file in the source tree
    """

    def real_decorator(func: F) -> F:
        """Wrapper"""

        @functools.wraps(func)
        def wrapper(*args: Any, **kwargs: Any) -> Callable:
            """Wrapper"""
            state_file = (
                f"{settings.CONFIG_FOLDER}/.build_state/file_hash_" + name + ".txt"
            )
            previous_hash = "catdog"
            if os.path.exists(state_file):
                with open(state_file) as old_file:
                    previous_hash = old_file.read()

            new_hash = hash_it(file)
            if new_hash == previous_hash:
                inform("Nothing changed, won't re-" + name)
                return lambda x: f"Skipping {name}, no change"
            if not os.path.exists(f"{settings.CONFIG_FOLDER}/.build_state"):
                os.makedirs(f"{settings.CONFIG_FOLDER}/.build_state")
            with open(state_file, "w+") as state:
                state.write(new_hash)
            try:

                return func(*args, **kwargs)
            except:  # noqa: B001
                # reset if step fails
                os.remove(state_file)
                raise

        return cast(F, wrapper)

    return cast(F, real_decorator)

def reset_build_state() -> None:
    """
    Delete all .build_state & to force all steps to re-run next build
    """
    if os.path.exists(f"{settings.CONFIG_FOLDER}/.build_state"):
        shutil.rmtree(f"{settings.CONFIG_FOLDER}/.build_state")
    if not os.path.exists(f"{settings.CONFIG_FOLDER}/.build_state"):
        os.makedirs(f"{settings.CONFIG_FOLDER}/.build_state")

def timed() -> F:
    """This decorator prints the execution time for the decorated function."""

    def real_decorator(func: F) -> F:
        """Wrapper"""

        @functools.wraps(func)
        def wrapper(*args: Any, **kwargs: Any) -> Callable:
            """Wrapper"""
            start = time.time()
            result = func(*args, **kwargs)
            end = time.time()
            inform("{} ran in {}s".format(func.__name__, round(end - start, 2)))
            return result

        return cast(F, wrapper)

    return cast(F, real_decorator)

def is_it_worse(task_name: str, current_rows: int, margin: int) -> bool:
    """
    Logic for dealing with a code base with very large amounts of lint.

    You will never fix it all, just don't make it worse.
    """
    if not os.path.exists(f"{settings.CONFIG_FOLDER}/.build_state"):
        os.makedirs(f"{settings.CONFIG_FOLDER}/.build_state")
    file_name = f"{settings.CONFIG_FOLDER}/.build_state/last_count_{task_name}.txt"

    last_rows = sys.maxsize
    if os.path.isfile(file_name):
        with open(file_name, "r+") as file:
            last_rows = int(file.read())
            if last_rows != current_rows:
                file.seek(0)
                file.write(str(current_rows))
                file.truncate()

    return current_rows > (last_rows + margin)


from flask import jsonify, request
from . import api
from .. import db
from ..models import Facility, Area
from ..decorators import json, paginate

@api.route('/areas/', methods=['GET'])
@json
@paginate('areas')
def get_areas():
    return Area.query

@api.route('/facilities/<int:id>/areas/', methods=['GET'])
@json
@paginate('areas')
def get_facility_areas(id):
    facility = Facility.query.get_or_404(id)
    return facility.areas

@api.route('/areas/<int:id>', methods=['GET'])
@json
def get_area(id):
    return Area.query.get_or_404(id)

@api.route('/facilities/<int:id>/areas/', methods=['POST'])
@json
def new_facility_area(id):
    facility = Facility.query.get_or_404(id)
    area = Area(facility=facility)
    area.import_data(request.json)
    db.session.add(area)
    db.session.commit()
    return {}, 201, {'Location': area.get_url()}

@api.route('/areas/<int:id>', methods=['PUT'])
@json
def edit_area(id):
    area = Area.query.get_or_404(id)
    area.import_data(request.json)
    db.session.add(area)
    db.session.commit()
    return {}

@api.route('/areas/<int:id>', methods=['DELETE'])
@json
def delete_area(id):
    area = Area.query.get_or_404(id)
    db.session.delete(area)
    db.session.commit()
    return {}


"""
Cosmic Ray General
==================
Code to model the evolution of the energy and position of cosmic ray protons emitted into the early universe IGM.
Essentially a set of common utility functions needed to compute the cosmic ray C coefficients and window functions.
"""

# Required modules
from typing import Union
import numpy as np
import scipy.constants
import os
from scipy.io import loadmat
from numba import njit
from scipy.special import gamma, hyp2f1

# Cosmology constants
H0 = 100*0.6704/3.086e19  # s^{-1}
OMEGA_M0 = 0.31687
OMEGA_B0 = 0.04902

fHe = 0.0732
H_FRAC = 1 - 4*fHe/(4*fHe + (1-fHe))

# Atomic Physics Constants
PROTON_M = 938.28  # In MeV/c^2
C_IN_MPC = scipy.constants.c / 3.086e22  # In Mpc s^{-1}
HI_ION_TIME_SCALE = 3e3 * 3.15e7  # From Sazonov & Sunyaev 2015, in s

# Derived Parameters
RHO_CRIT = 3 * H0**2 / (8 * np.pi * scipy.constants.G * 1e6)  # In kg cm^{-3}
TAU_INTERM = HI_ION_TIME_SCALE * scipy.constants.m_p / (RHO_CRIT * H_FRAC * OMEGA_B0)  # In s
KE_DECAY_CONST = 1**(3/2)/(H0*np.sqrt(OMEGA_M0)*TAU_INTERM)  # In MeV^{3/2}

# Model Parameters
KE_REL_TRANS_MEV = 100  # Kinetic energy of the relativistic to non-relativistic transition in MeV

# Simulation Parameters
LPIX = 3  # comoving Mpc

# General Utility Functions
@njit(cache=True)
def power_law_spectrum(ke: float, alpha: float = -2, ke_min: float = 1e-3, ke_max: float = 1e8) -> float:
    """ Probability density of a truncated power-law distribution normalized to a total of 1 MeV of energy

    Parameters
    ----------
    ke : Float
        Kinetic energy to evaluate at in MeV
    alpha : Float
        Exponent of the power-law, defaults to -2
    ke_min : Float
        Minimum KE cutoff of the power-law in MeV, defaults to 1e-3
    ke_max : Float
        Maximum KE cutoff of the power-law in MeV, defaults to 1e8

    Returns
    -------
    Float
        Normalized probability density in MeV^-2
    """
    if alpha == -2:
        nf = 1/(np.log(ke_max) - np.log(ke_min))
    else:
        nf = (alpha + 2)/(ke_max**(alpha + 2) - ke_min**(alpha + 2))
    return nf * (ke**alpha) * (ke <= ke_max) * (ke >= ke_min)

# Heating Functions
@njit(cache=True)
def normalized_heating_rate(ke: float, zs: float) -> float:
    """ Compute heating rate (normalized to remove fheat factor) of the IGM by CR protons by excitation and ionization

    Parameters
    ----------
    ke : Float
        Kinetic energies in MeV of the proton
    zs : Float
        Redshift heating rate is being evaluated at

    Returns
    -------
    Float
        Heating rate in MeV per redshift of the given proton
    """
    return KE_DECAY_CONST * ((1 + zs) ** (1/2)) * (ke ** (-1/2))

# CR Energy Evolution Functions
@njit(cache=True)
def initial_ke_rel(ke_now: float, z_now: float, z_ini: float) -> float:
    """ Initial kinetic energy of a CR proton if always in the relativistic regime

    Parameters
    ----------
    ke_now : Float
        Kinetic energy of protons now in MeV
    z_now : Float
        Redshift now
    z_ini : Float
        Redshift when proton was emitted

    Returns
    -------
    Float
        Kinetic energy of the proton when emitted in MeV
    """
    p_sq_factor = (ke_now**2 + 2*ke_now*PROTON_M) * (((1 + z_ini)/(1 + z_now))**2)
    return -PROTON_M + np.sqrt(PROTON_M**2 + p_sq_factor)

@njit(cache=True)
def current_ke_rel(ke_ini: float, z_now: float, z_ini: float) -> float:
    """ Current kinetic energy of a CR proton if always in the relativistic regime

    Parameters
    ----------
    ke_ini : Float
        Initial kinetic energy of the proton in MeV
    z_now : Float
        Redshift now
    z_ini : Float
        Redshift when proton was emitted

    Returns
    -------
    Float
        Kinetic energy of the proton at z_now in MeV
    """
    p_sq_factor = (ke_ini**2 + 2*ke_ini*PROTON_M) * (((1 + z_now)/(1 + z_ini))**2)
    return -PROTON_M + np.sqrt(PROTON_M**2 + p_sq_factor)

@njit(cache=True)
def initial_ke_non_rel(ke_now: float, z_now: float, z_ini: float) -> float:
    """ Initial kinetic energy of a CR proton if always in the non-relativistic regime

    Parameters
    ----------
    ke_now : Float
        Kinetic energy of protons now in MeV
    z_now : Float
        Redshift now
    z_ini : Float
        Redshift when proton was emitted

    Returns
    -------
    Float
        Kinetic energy of the proton when emitted in MeV
    """
    adb_factor = (1 + z_ini)**2 / (1 + z_now)**2
    return adb_factor * (ke_now**(3/2) + KE_DECAY_CONST*((1+z_now)**(3/2) -
                                                         ((1 + z_now)**3)*((1 + z_ini)**(-3/2))))**(2/3)

@njit(cache=True)
def current_ke_non_rel(ke_ini: float, z_now: float, z_ini: float) -> float:
    """ Current kinetic energy of a CR proton if always in the non-relativistic regime

    Parameters
    ----------
    ke_ini : Float
        Initial kinetic energy of the proton in MeV
    z_now : Float
        Redshift now
    z_ini : Float
        Redshift when proton was emitted

    Returns
    -------
    Float
        Kinetic energy of the proton at z_now in MeV
    """
    adb_factor = (1 + z_now)**2 / (1 + z_ini)**2
    return adb_factor * (ke_ini**(3/2) - KE_DECAY_CONST*(((1+z_now)**(-3/2))*((1 + z_ini)**3) -
                                                         (1 + z_ini)**(3/2)))**(2/3)

@njit(cache=True)
def transition_redshifts(ke_now: float, z_now: float) -> float:
    """ Compute redshift a proton would have transitioned between the relativistic and non-relativistic regime

    Parameters
    ----------
    ke_now : Float
        Kinetic energy of proton now in MeV
    z_now : Float
        Redshift now

    Returns
    -------
    Float
        Redshift of relativistic to non-relativistic transition
    """
    ke_parameter = KE_DECAY_CONST*((1 + z_now)**(-3/2)) + (ke_now**(3/2))/((1 + z_now)**3)
    return ((-KE_DECAY_CONST + np.sqrt(KE_DECAY_CONST**2 + 4*ke_parameter*(KE_REL_TRANS_MEV**(3/2)))) /
            (2*(KE_REL_TRANS_MEV**(3/2))))**(-2/3) - 1

@njit(cache=True)
def initial_ke(ke_now: float, z_now: float, z_ini: float) -> float:
    """ Initial kinetic energy of an arbitrary CR proton

    Parameters
    ----------
    ke_now : Float
        Kinetic energy of proton now in MeV
    z_now : Float
        Redshift now
    z_ini : Float
        Redshift when proton was emitted

    Returns
    -------
    Float
        Kinetic energy of the proton when emitted in MeV
    """

    # Assume initially no transitions occur
    if ke_now > KE_REL_TRANS_MEV:
        ini_ke = initial_ke_rel(ke_now, z_now, z_ini)
    else:
        ini_ke = initial_ke_non_rel(ke_now, z_now, z_ini)
        if ini_ke > KE_REL_TRANS_MEV:  # Check if transition occurs
            ini_ke = initial_ke_rel(KE_REL_TRANS_MEV, transition_redshifts(ke_now, z_now), z_ini)
    return ini_ke

@njit(cache=True)
def current_ke(ke_ini: float, z_now: float, z_ini: float) -> float:
    """ Current kinetic energy of an arbitrary CR proton

    Parameters
    ----------
    ke_ini : Float
        Initial kinetic energy of the proton in MeV
    z_now : Float
        Redshift now
    z_ini : Float
        Redshift when proton was emitted

    Returns
    -------
    Float
        Kinetic energy of the proton at z_now in MeV
    """

    # If at a non-relativistic initial energy then it stays non-relativistic
    if ke_ini <= KE_REL_TRANS_MEV:
        ke_now = current_ke_non_rel(ke_ini, z_now, z_ini)

    # Otherwise, starts relativistic and might transition to non-relativistic later
    else:
        ke_now = current_ke_rel(ke_ini, z_now, z_ini)
        if ke_now < KE_REL_TRANS_MEV:  # Check if transition occurs
            z_trans = (1 + z_ini) * (((KE_REL_TRANS_MEV**2 + 2*KE_REL_TRANS_MEV*PROTON_M) /
                                      (ke_ini**2 + 2*ke_ini*PROTON_M))**(1/2)) - 1
            ke_now = current_ke_non_rel(KE_REL_TRANS_MEV, z_now, z_trans)

    return ke_now

@njit(cache=True)
def jacobian_factor(ke_now: float, z_now: float, z_ini: float) -> float:
    """ Jacobian factor that transforms between density of KE now and KE at emission

    Branch-less implementation for numpy arrays

    Parameters
    ----------
    ke_now : Float
        Kinetic energy of proton now in MeV
    z_now : Float
        Redshift now
    z_ini : Float
        Redshift when proton was emitted

    Returns
    -------
    Float
        Jacobian factor (dimensionless) dT'/dT
    """

    # Get initial KE of proton
    ini_ke = initial_ke(ke_now, z_now, z_ini)

    # Assume initially no transitions occur
    if ke_now > KE_REL_TRANS_MEV:
        jacob = ((ke_now + PROTON_M)/(ini_ke + PROTON_M))*(((1 + z_ini) / (1 + z_now))**2)
    else:
        if ini_ke < KE_REL_TRANS_MEV:
            jacob = np.sqrt(ke_now / ini_ke)*(((1 + z_ini) / (1 + z_now))**3)
        else:  # Transitioning case
            jacob = ((KE_REL_TRANS_MEV + PROTON_M) / (ini_ke + PROTON_M)) * np.sqrt(ke_now / KE_REL_TRANS_MEV) * \
                    (((1 + z_ini) ** 2) * (1 + transition_redshifts(ke_now, z_now)) / ((1 + z_now) ** 3))
    return jacob

# CR Path Length Functions
def g_1_func(x: np.ndarray) -> np.ndarray:
    """ Special function required to compute path length in the non-relativistic regime

    G_1 == 2F1(-1/3, -1/3; 2/3; x)

    Parameters
    ----------
    x : Array
        Arguments to evaluate G_1 at

    Returns
    -------
    Array
        G_1(x)
    """

    # Input sanitization
    if np.any(x < 0):
        raise ValueError(f'One or more of the given arguments is outside the range 0 <= x <= 1 for which '
                         f'this function is defined.')

    # Initialize data structure for the output
    output = np.empty(np.shape(x), dtype=float)

    # Consider the two special boundary cases
    output[x == 0] = 1
    output[x >= 1] = gamma(2/3)*gamma(4/3)

    # General case
    output[np.logical_and(x > 0, x < 1)] = hyp2f1(-1/3, -1/3, 2/3, x[np.logical_and(x > 0, x < 1)])
    return output

def g_2_func(x: np.ndarray) -> np.ndarray:
    """ Special function required to compute path length in the relativistic regime

        G_2 == 2F1(1/4, 1/2; 5/4; -x^2)      x in R

        Parameters
        ----------
        x : Array
            Arguments to evaluate G_2 at, x must be real

        Returns
        -------
        Array
            G_2(x)
        """
    return hyp2f1(1/4, 1/2, 5/4, -np.power(x, 2))

def alpha_1_parameter(ke_0: np.ndarray, z_0: float) -> np.ndarray:
    """ Dimensionless parameter required to compute path length in the non-relativistic regime

    alpha_1 == ((H_0 sqrt{Omega_m} tau_{inter} ke_0^{3/2})/((1 MeV)^{3/2}(1+z_0)^3)  + (1+z_0)^{-3/2})^{-2/3}

    Equivalently alpha_1 can be interpreted as 1 + z_{abs}, the inverse scale factor when a cosmic ray proton would
    be thermalized with the IGM.

    Parameters
    ----------
    ke_0 : Array
        Initial KE of the protons in MeV
    z_0: Float
        Emission redshift of the proton

    Returns
    -------
    Array
        alpha_1 parameter evaluated for each of these protons
    """
    return (((ke_0**(3/2)) * ((1 + z_0)**(-3)))/KE_DECAY_CONST + (1 + z_0)**(-3/2))**(-2/3)

def alpha_2_parameter(ke_0: np.ndarray, z_0: float) -> np.ndarray:
    """ Dimensionless parameter required to compute path length in the relativistic regime

    alpha_2 == sqrt(T_0^2/(m_p^2 c^4) + 2 T_0 / (m_pc^2)) / (1 + z_0)

    Parameters
    ----------
    ke_0 : Array
        Initial KE of the protons in MeV
    z_0: Float
        Emission redshift of the proton

    Returns
    -------
    Array
        alpha_2 parameter evaluated for each of these protons
    """
    norm_ke = ke_0 / PROTON_M
    return np.sqrt(norm_ke**2 + 2*norm_ke) / (1 + z_0)

def comoving_path_length_non_rel(z: float, ke_0: Union[float, np.ndarray], z_0: Union[float, np.ndarray]) -> np.ndarray:
    """ Comoving path length a non-relativistic proton would travel between z_0 and z

    Note for performance it is assumed that none of the protons have been absorbed, any such protons should be filtered
    out before calling this function

    Parameters
    ----------
    z : Float
        Redshift proton is at now
    ke_0 : Array
        Initial kinetic energies of the protons in MeV
    z_0 : Float
        Source redshift of protons

    Returns
    -------
    Array
        Comoving path lengths travelled in comoving Megaparsec
    """
    # Pre-computations
    alpha_1 = alpha_1_parameter(ke_0, z_0)
    prefactor = (C_IN_MPC/H0) * ((8/(OMEGA_M0 * PROTON_M))**(1/2)) * (KE_DECAY_CONST**(1/3)) * (alpha_1**(-1/2))

    # Primary computation
    output = ((1 + z_0)**(1/2))*g_1_func((alpha_1/(1 + z_0))**(3/2)) - \
             ((1 + z)**(1/2))*g_1_func((alpha_1/(1 + z))**(3/2))
    return prefactor*output

def comoving_path_length_rel(z: float, ke_0: np.ndarray, z_0: float) -> np.ndarray:
    """ Comoving path length a relativistic proton would travel between z_0 and z

    Parameters
    ----------
    z : Float
        Redshift proton is at now
    ke_0 : Array
        Initial kinetic energies of the protons in MeV
    z_0 : Float
        Source redshift of protons

    Returns
    -------
    Array
        Comoving path lengths travelled in comoving Megaparsec
    """
    # Pre-computations
    alpha_2 = alpha_2_parameter(ke_0, z_0)
    prefactor = 2 * C_IN_MPC * alpha_2 / (H0 * np.sqrt(OMEGA_M0))

    # Primary computation
    output = np.sqrt(1 + z_0)*g_2_func(alpha_2*(1 + z_0)) - np.sqrt(1 + z)*g_2_func(alpha_2*(1 + z))
    return prefactor*output

def comoving_path_proton(z: float, ke_0: np.ndarray, z_0: float, rel_only: bool = False) -> np.ndarray:
    """ Comoving path length a CR proton would travel between z_0 and z

    Note for performance it is assumed that none of the protons have been absorbed, any such protons should be filtered
    out before calling this function

    Parameters
    ----------
    z : Float
        Redshift proton is at now
    ke_0 : Array
        Initial kinetic energies of the protons in MeV
    z_0 : Float
        Source redshift of protons
    rel_only : Boolean
        Flag to only use the relativistic regime solution rather than the full solution

    Returns
    -------
    Array
        Comoving path lengths travelled in comoving Megaparsec
    """
    # Datastructure for output
    output = np.empty(np.shape(ke_0), dtype=float)

    # Handle relativistic only case
    if rel_only:
        output = comoving_path_length_rel(z, ke_0, z_0)
        return output

    # Otherwise, the simplest case is if starts non-relativistic, then as they only lose energy it stays
    # non-relativistic
    output[ke_0 <= KE_REL_TRANS_MEV] = comoving_path_length_non_rel(z, ke_0[ke_0 <= KE_REL_TRANS_MEV], z_0)

    # For relativistic case we have the added complication that the proton can change into the non-relativistic regime
    # if sufficient time has passed, this transition would occur at a redshift
    z_rel_trans = (1 + z_0) * np.sqrt((KE_REL_TRANS_MEV ** 2 + 2 * KE_REL_TRANS_MEV * PROTON_M)
                                      / (ke_0 ** 2 + 2 * ke_0 * PROTON_M)) - 1

    # If z >= z_rel_trans then this transition has not occurred yet, and so we do not have to deal with it
    output[np.logical_and(ke_0 > KE_REL_TRANS_MEV, z >= z_rel_trans)] = \
        comoving_path_length_rel(z, ke_0[np.logical_and(ke_0 > KE_REL_TRANS_MEV, z >= z_rel_trans)], z_0)

    # If z < z_rel_trans then we do need to handle the transition by summing the seperated Delta Rs for each part of the
    # protons journey
    slice_trans_p = np.logical_and(ke_0 > KE_REL_TRANS_MEV, z < z_rel_trans)
    output[slice_trans_p] = comoving_path_length_rel(z_rel_trans[slice_trans_p], ke_0[slice_trans_p], z_0) + \
        comoving_path_length_non_rel(z, KE_REL_TRANS_MEV, z_rel_trans[slice_trans_p])
    return output


from dataoutput.write_timetables import writingTimetables
from .schulmodell import createModel
from loguru import logger as loguru_logger

# from .schulmodell import Datenbankzugriff
import pyomo.environ as pyo

# from pyomo.environ import
import os

# Aus schulmodell.py
from pyomo.environ import *
import cProfile
import time
import datetime

# import von Django Model Klassen
from datainput.models import (
    Raum,
    Lehrer,
    Schulklasse,
    Schulfach,
    Lehrfaecher,
    Unterrricht,
    Tag,
    Stunde,
    Slot,
    Partner,
    LehrerBelegt,
    RaumBelegt,
    VorgabeEinheit,
    Uebergreifung,
    LehrerBelegt,
    StundenzahlproTag,
)
from optimierung.models import Parameters

"""
Hier wird das Optimierungsmodell an den jeweils ausgewählten Solver weitergegeben.
"""
"""Aktuell wird nur künstlich ein Run kreiert und eine bereits vorbereitete Resultdatei ausgewertet.
Später muss das Ergebnis CSV Dateiblatt im Optimierung ordner gespeichert werden. Dann kann get getTimetableUnits seine Arbeit richtig machen
"""

def doeverything():

    """ Senden an den solver und festlegen des Ouputorts
    Supported Solvers: CBC, Gurobi, Cplex
    all solvers with pyomo help --solvers
    """

    # check if file exists, then delete it if so
    if os.path.exists("./X_res.csv"):
        os.remove("./X_res.csv")

    loguru_logger.info("START: create model")
    # import solver and solve model, wirte documentation and load results
    model = createModel()
    loguru_logger.info("END: create model")
    # model.pprint('model-debug.txt')
    # see https://github.com/PyUtilib/pyutilib/issues/31#issuecomment-382479024
    import pyutilib.subprocess.GlobalData

    pyutilib.subprocess.GlobalData.DEFINE_SIGNAL_HANDLERS_DEFAULT = False
    # opt = pyo.SolverFactory('glpk', executable='/usr/local/bin/glpsol')
    loguru_logger.info("initialize solver")
    opt = pyo.SolverFactory("gurobi")
    # instance = model.create_instance()
    loguru_logger.info("START: solve model")
    # solve the model with a mip gap of 2%
    results = opt.solve(model, options=dict(mipgap=0.07), tee=True)
    loguru_logger.info("END: solve model")
    results.write()
    loguru_logger.info(f"SOLVER STATUS: {str(results.solver.status)}")

    loguru_logger.info("Write model file")
    model.write("model.lp", io_options={"symbolic_solver_labels": True})

    print("optimierung done")

    if (results.solver.status == SolverStatus.ok) and (
        results.solver.termination_condition == TerminationCondition.optimal
    ):
        loguru_logger.info("write results file")
        #model.display("results.txt")
        loguru_logger.info("START: write x values")
        with open("./X_res.csv", "w") as file:
            file.write("Klasse, Lehrer, Fach, Slot, Var\n")
            for (k, l, f, s) in model.Variablenmenge:
                if pyo.value(model.x[k, l, f, s] > 0.1):
                    file.write(f"{k},{l},{f},{s},{pyo.value(model.x[k, l, f, s])}\n")
        loguru_logger.info("END: write x values")
        loguru_logger.info("Transfer solution to timetables")
        """Benutze kreiertes Ergebnis, um Lehreinheiten zu erstellen"""
        writingTimetables.getTimetableUnits()
    elif results.solver.termination_condition == TerminationCondition.infeasible:
        loguru_logger.warning("Solver status is infeasible!")
    else:
        loguru_logger.warning(
            "Solver status is not optimal and not infeasible (possibly unbounded)!"
        )


from flask import Flask, render_template, request, redirect, session, flash, url_for
# import the Connector function
from mysqlconnection import MySQLConnector
import re
# was unable to create a single regex expression for name.  Need to work more on regex
EMAIL_REGEX = re.compile(r'^[a-zA-Z0-9\.\+_-]+@[a-zA-Z0-9\._-]+\.[a-zA-Z]*$')

app = Flask(__name__)
app.secret_key = 'supersecret'
# connect and store the connection in "mysql" note that you pass the database name to the function
mysql = MySQLConnector(app, 'emaildb')
# an example of running a query

@app.route('/')
def index():
    return render_template("index.html")

@app.route('/update', methods=['POST'])
def update():
    query = "SELECT email FROM emails"                           # define your query
    emails = mysql.query_db(query)
    errors = False
    if len(request.form['email']) < 1:
        errors = True
        flash("e-mail address is empty. Enter e-mail.")
    elif not EMAIL_REGEX.match(request.form['email']):
        errors = True
        flash("Invalid e-mail address. Enter e-mail.")
    elif {'email' : request.form['email']} in emails:
        errors = True
        flash("The e-mail address entered already exists in the database.")
    if errors:
        return redirect('/')
    else:
        session['insert'] = 1
        query = "INSERT INTO emails (email, created_at, updated_at) VALUES (:email, NOW(), NOW())"
        data = {'email': request.form['email']}
        mysql.query_db(query, data)
        return redirect('/success')

@app.route('/success')
def email_list():
    query = "SELECT * FROM emails"                           # define your query
    emails = mysql.query_db(query)                           # run query with query_db()
    return render_template('email_list.html', list_emails=emails, insert = session['insert']) # pass data to our template

@app.route('/remove', methods=['POST'])
def remove():
    session['insert'] = 0
    errors = False
    if len(request.form['email']) < 1:
        errors = True
        flash("e-mail address field is empty. Enter e-mail to remove and re-submit.")
    elif not EMAIL_REGEX.match(request.form['email']):
        errors = True
        flash("Invalid e-mail address. Enter e-mail.")
    else:
        query = "SELECT email FROM emails"                           # define your query
        emails = mysql.query_db(query)
        if not {'email' : request.form['email']} in emails:
            errors = True
            flash("The e-mail address entered was not found in the database.")
    if errors:
        return redirect('/success')
    else:
        query = "DELETE FROM emails WHERE email = :email"
        data = {'email': request.form['email']}
        mysql.query_db(query, data)
        return redirect('/success')

if __name__ == "__main__":
    app.run(debug=True)


# -*- coding: utf-8 -*-
import os
import tensorflow as tf
import numpy as np
from non_local_tf import non_local_block
tf.enable_eager_execution()

n_class = 100

layers = tf.keras.layers
class CNN(tf.keras.Model):
    def __init__(self, non_local=True):
        super(CNN, self).__init__()
        self.non_local = non_local
        self.conv1 = layers.Conv2D(16, (3,3), padding='same')
        self.pool1 = layers.MaxPool2D(2)
        self.conv2 = layers.Conv2D(32, (3,3), padding='same')
        self.pool2 = layers.MaxPool2D(2)
        self.conv3 = layers.Conv2D(64, (3,3), padding='same')
        self.pool3 = layers.MaxPool2D(2)
        self.conv4 = layers.Conv2D(128, (3,3), padding='same')
        self.conv5 = layers.Conv2D(128, (3,3), padding='same')
        self.avgPool = layers.AveragePooling2D((4,4))
        self.dense = layers.Dense(n_class)

        # non-local
        if non_local == True:
            self.atten1 = non_local_block(input_channel=128, input_shape=4, mode='embedded')
            self.atten2 = non_local_block(input_channel=128, input_shape=4, mode='embedded')

    def predict(self, x):
        x = self.conv1(x)  # (batchsize, 32, 32, 16)
        x = tf.nn.relu(x)
        x = self.pool1(x)

        x = self.conv2(x)  # (batchsize, 16, 16, 32)
        x = tf.nn.relu(x)
        x = self.pool2(x)

        x = self.conv3(x)   # (batchsize, 8, 8, 64)
        x = tf.nn.relu(x)
        x = self.pool3(x)

        x = self.conv4(x)    # (batchsize, 4, 4, 128)
        x = tf.nn.relu(x)
        [x, w1] = self.atten1(x) if self.non_local else [x, None]
        x = self.conv5(x)    # (batchsize, 4, 4, 128)
        x = tf.nn.relu(x)
        [x, w2] = self.atten2(x) if self.non_local else [x, None]
        x = self.avgPool(x)  # (batchsize, 1, 1, 128)
        x = tf.squeeze(x)    # (batchsize, 128)

        logits = self.dense(x)   # (batchsize, 100)
        return logits, [w1,w2]

    def loss_fn(self, X, y):
        preds, _ = self.predict(X)
        loss = tf.losses.sparse_softmax_cross_entropy(labels=y, logits=preds)
        return loss

    def acc_fn(self, X, y):
        preds = self.predict(X)[0].numpy()
        acc = np.sum(np.argmax(preds, axis=1) == y.numpy(), dtype=np.float32) / X.numpy().shape[0]
        return acc

def train(model, dataset, test_data, test_labels, optimizer, epoches, logdir):
    train_losses, val_losses, val_accs = [], [], []
    test_data, test_labels = tf.convert_to_tensor(test_data), tf.convert_to_tensor(test_labels)
    best_acc = 0.
    # train
    for epoch in range(epoches):
        losses = []
        for (batch, (inp, targ)) in enumerate(dataset):
            with tf.GradientTape() as tape:
                loss = model.loss_fn(inp, targ)
            gradients = tape.gradient(loss, model.trainable_variables)
            # print("loss: ", loss.numpy(), ",\tacc: ", model.acc_fn(inp, targ)*100, "%")
            optimizer.apply_gradients(zip(gradients, model.variables))
            losses.append(loss)

        print("Epoch :", epoch, ", train loss :", np.mean(losses))
        val_loss = model.loss_fn(test_data, test_labels)
        val_acc = model.acc_fn(test_data, test_labels)
        print("Epoch :", epoch, ", val loss", val_loss.numpy(),", valid acc:", val_acc*100, "%")
        train_losses.append(np.mean(losses))
        val_losses.append(val_loss)
        val_accs.append(val_acc)
        if val_acc > best_acc:
            # model.save_weights(os.path.join(logdir, "model_best_"+str(val_acc)+".h5"))
            model.save_weights(os.path.join(logdir, "model_best.h5"))
            best_acc = val_acc
    return np.array(train_losses), np.array(val_losses), np.array(val_accs)

if __name__ == '__main__':
    # dataset
    (X_train, y_train), (X_test, y_test) = tf.keras.datasets.cifar100.load_data()
    X_train = X_train.astype(np.float32) / 255.
    X_test = X_test.astype(np.float32) / 255.
    y_train = y_train.reshape(y_train.shape[0]).astype(np.int)
    y_test = y_test.reshape(y_test.shape[0]).astype(np.int)

    dataset = tf.data.Dataset.from_tensor_slices((X_train, y_train)).shuffle(50000)
    dataset = dataset.batch(64, drop_remainder=True)

    # model
    non_local = True
    learning_rate = tf.Variable(1e-3, name="learning_rate")
    optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)
    model = CNN(non_local=non_local)

    # dir
    logdir = "log-non-local" if non_local else "log"
    if not os.path.exists(logdir):
        os.makedirs(logdir)

    # train
    loss1, loss2, accs = train(model, dataset, X_test, y_test, optimizer, epoches=30, logdir=logdir)

    # plot
    import matplotlib.pyplot as plt
    plt.figure(figsize=(8, 8))
    plt.plot(np.arange(loss1.shape[0]), loss1)
    plt.plot(np.arange(loss2.shape[0]), loss2)
    plt.legend(["train loss", "val loss"])
    plt.grid()
    plt.savefig(os.path.join(logdir, "loss.png"))
    plt.close()

    plt.figure(figsize=(8, 8))
    plt.plot(np.arange(accs.shape[0]), accs)
    plt.legend(["val acc"])
    plt.grid()
    plt.savefig(os.path.join(logdir, "acc.png"))
    plt.close()


import unittest
from itertools import zip_longest

import numpy as np
import pandas as pd

from base_types import ExcelConstants, CellValue, CellPosition, CellOffset, ExcelCell, NeighborCell, \
    NeighborsContainer, CellOffsetAction, Indexes
from value_finders import ExactValueFinder, ExactValuesFinder, RegexFinder, StartWithFinder, EndWithFinder
from position_finders import FirstRowNumFinder, FirstColNumFinder, FirstCellPositionFinder, AllRowNumsFinder, \
    AllCellPositionsFinder, AllColNumsFinder
from data import simple_data, duplicates_data

class TestTDS(unittest.TestCase):
    def setUp(self):
        self.simple_df = pd.DataFrame.from_dict(data=simple_data['data'], columns=simple_data['columns'],
                                                orient='index')
        self.duplicates_df = pd.DataFrame.from_dict(data=duplicates_data['data'], columns=duplicates_data['columns'],
                                                    orient='index')

    @unittest.skip
    def test_cell_position(self):
        cells_offsets_results = [
            (CellOffset(col=-1, row=-1), CellPosition(col=0, row=0)),
            (CellOffset(col=0, row=-1), CellPosition(col=1, row=0)),
            (CellOffset(col=1, row=-1), CellPosition(col=2, row=0)),
            (CellOffset(col=1, row=0), CellPosition(col=2, row=1)),
            (CellOffset(col=1, row=1), CellPosition(col=2, row=2)),
            (CellOffset(col=0, row=1), CellPosition(col=1, row=2)),
            (CellOffset(col=-1, row=1), CellPosition(col=0, row=2)),
            (CellOffset(col=-1, row=0), CellPosition(col=0, row=1)),
            (CellOffset(col=0, row=0), CellPosition(col=1, row=1)),
            (CellOffset(col=-2, row=-2), CellPosition(col=-1, row=-1)),
            (CellOffset(col=1, row=-2), CellPosition(col=2, row=-1)),
            (CellOffset(col=-2, row=1), CellPosition(col=-1, row=2)),
        ]
        for cell_offset, result_cell_position in cells_offsets_results:
            self.assertEqual(CellPosition(col=1, row=1) + cell_offset, result_cell_position)

        cells_positions_results = [
            (CellPosition(col=1, row=1), CellPosition(col=2, row=2)),
            (CellPosition(col=2, row=2), CellPosition(col=3, row=3)),
            (CellPosition(col=1, row=2), CellPosition(col=2, row=3)),
            (CellPosition(col=2, row=1), CellPosition(col=3, row=2)),
        ]
        for cell_position, result_cell_position in cells_positions_results:
            self.assertEqual(CellPosition(col=1, row=1) + cell_position, result_cell_position)

        incomplete_cells_positions_results = [
            (CellPosition(), CellPosition(), CellPosition()),
            (CellPosition(col=1), CellPosition(col=1), CellPosition(col=2)),
            (CellPosition(row=1), CellPosition(row=1), CellPosition(row=2)),
            (CellPosition(col=1), CellPosition(row=1), CellPosition(col=1, row=1)),
            (CellPosition(col=1, row=1), CellPosition(row=1), CellPosition(col=1, row=2)),
            (CellPosition(col=1, row=1), CellPosition(col=1), CellPosition(col=2, row=1)),
        ]
        for first_cell_position, second_cell_position, result_cell_position in incomplete_cells_positions_results:
            self.assertEqual(first_cell_position + second_cell_position, result_cell_position)

        self.assertTrue(CellPosition(col=1))
        self.assertTrue(CellPosition(row=1))
        self.assertTrue(CellPosition(col=1, row=1))
        self.assertFalse(CellPosition())
        self.assertEqual(CellPosition(), CellPosition())
        self.assertNotEqual(CellPosition(), CellPosition(col=1))
        self.assertNotEqual(CellPosition(), CellPosition(row=1))
        self.assertNotEqual(CellPosition(), CellPosition(col=1, row=1))

    @unittest.skip
    def test_cell_rownum_finder(self):
        cell_values_results = [
            (CellValue('SKU'), CellPosition(row=0)),
            (CellValue('*ГЛИЦИН-КАНОН ТБ ПОДЪЯЗЫЧ 1000 МГ №10'), CellPosition(row=5)),
            (CellValue(7458), CellPosition(row=10)),
            (CellValue(274254), CellPosition(row=15)),
            (CellValue(234637), CellPosition(row=20)),
            (CellValue(3012), CellPosition(row=25)),
            (CellValue('Общий итог'), CellPosition(row=0)),
            (CellValue('Qwerty'), CellPosition()),
            (CellValue(-999), CellPosition()),
        ]
        finder = FirstRowNumFinder(df=self.simple_df)
        for cell_value, result_position in cell_values_results:
            finder.value_finder = ExactValueFinder(cell_value=cell_value)
            self.assertEqual(finder.get_position(), result_position)

    @unittest.skip
    def test_cell_colnum_finder(self):
        cell_values_results = [
            (CellValue('SKU'), CellPosition(col=0)),
            (CellValue(2012), CellPosition(col=1)),
            (CellValue(2013), CellPosition(col=2)),
            (CellValue(2014), CellPosition(col=3)),
            (CellValue(2015), CellPosition(col=4)),
            (CellValue(2016), CellPosition(col=5)),
            (CellValue('Общий итог'), CellPosition(col=0)),
            (CellValue('Qwerty'), CellPosition()),
            (CellValue(-999), CellPosition()),

        ]
        finder = FirstColNumFinder(df=self.simple_df)
        for cell_value, result_positions in cell_values_results:
            finder.value_finder = ExactValueFinder(cell_value)
            self.assertEqual(finder.get_position(), result_positions)

    @unittest.skip
    def test_cell_position_finder(self):
        cell_values_results = [
            (CellValue('SKU'), CellPosition(col=0, row=0)),
            (CellValue('Общий итог'), CellPosition(col=6, row=0)),
            (CellValue(7458), CellPosition(col=1, row=10)),
            (CellValue(6059), CellPosition(col=5, row=16)),
            (CellValue('ЭКСХОЛ КАПС 250 МГ №10'), CellPosition(col=0, row=197)),
            (CellValue(105153489.25), CellPosition(col=6, row=204)),
        ]
        finder = FirstCellPositionFinder(df=self.simple_df)
        for cell_value, result_position in cell_values_results:
            finder.value_finder = ExactValueFinder(cell_value)
            self.assertEqual(finder.get_position(), result_position)

    @unittest.skip
    def test_excell_cell(self):
        self.assertEqual(ExcelCell(cell_name='A1').cell_position, CellPosition(col=0, row=0))
        excell_cell = ExcelCell(cell_name='b2')
        self.assertEqual(excell_cell.cell_position, CellPosition(col=1, row=1))
        self.assertLessEqual(ExcelCell(cell_name='A1'), ExcelCell(cell_name='B1'))
        self.assertLessEqual(ExcelCell(cell_name='A1'), ExcelCell(cell_name='A2'))
        self.assertLess(ExcelCell(cell_name='A1'), ExcelCell(cell_name='B2'))
        self.assertEqual(ExcelCell(cell_name='A1'), ExcelCell(cell_name='A1'))

        cells_offsets_results = [
            (CellOffset(col=-1, row=-1), ExcelCell(cell_name='A1')),
            (CellOffset(col=0, row=-1), ExcelCell(cell_name='B1')),
            (CellOffset(col=1, row=-1), ExcelCell(cell_name='C1')),
            (CellOffset(col=1, row=0), ExcelCell(cell_name='C2')),
            (CellOffset(col=1, row=1), ExcelCell(cell_name='C3')),
            (CellOffset(col=0, row=1), ExcelCell(cell_name='B3')),
            (CellOffset(col=-1, row=1), ExcelCell(cell_name='A3')),
            (CellOffset(col=-1, row=0), ExcelCell(cell_name='A2')),
            (CellOffset(col=0, row=0), ExcelCell(cell_name='B2')),
        ]
        for cell_offset, result_excell_cell in cells_offsets_results:
            self.assertEqual(ExcelCell(cell_name='B2') + cell_offset, result_excell_cell)

        self.assertRaises(Exception, ExcelCell, cell_name='b2c')
        self.assertRaises(Exception, ExcelCell, cell_name='2c')
        self.assertRaises(Exception, ExcelCell, cell_name='b')
        self.assertRaises(Exception, ExcelCell, cell_name='1')
        self.assertRaises(Exception, ExcelCell, cell_name='a@')
        self.assertRaises(Exception, ExcelCell, cell_name='a1-')
        self.assertRaises(Exception, ExcelCell, cell_name='aaaaa1')
        self.assertRaises(Exception, ExcelCell, cell_name='ш1')
        self.assertRaises(Exception, ExcelCell, cell_name='a' + str(ExcelConstants.MAX_EXCEL_ROWS_COUNT + 10))

    @unittest.skip
    def test_all_row_nums_finder(self):
        finder = AllRowNumsFinder(df=self.duplicates_df)
        cell_values_results = [
            (
                CellValue('*АРИТЕЛ ПЛЮС ТБ П/О 2,5МГ+6,25МГ №30'),
                [CellPosition(row=1), CellPosition(row=3)]
            ),
            (
                CellValue(1099),
                [CellPosition(row=2), CellPosition(row=7), CellPosition(row=9)]
            ),
            (
                CellValue(''),
                [CellPosition(row=1), CellPosition(row=5), CellPosition(row=12)]
            ),
            (
                CellValue(),
                [CellPosition(row=1), CellPosition(row=5), CellPosition(row=12)]
            ),
            (
                CellValue(0),
                [CellPosition(row=counter) for counter in range(1, 11)]
            ),
            (CellValue('Qwerty'), []),
            (CellValue(-999), [])
        ]
        for cell_value, expected_result_positions in cell_values_results:
            finder.value_finder = ExactValueFinder(cell_value=cell_value)
            fact_result_positions = finder.get_all_positions()
            self.assertEqual(len(expected_result_positions), len(fact_result_positions))
            if fact_result_positions:
                zip_result = zip_longest(fact_result_positions, expected_result_positions)
                for fact_position, expected_position in zip_result:
                    self.assertEqual(fact_position, expected_position)
            else:
                self.assertEqual(fact_result_positions, expected_result_positions)
                self.assertEqual(fact_result_positions, cell_values_results[-1][1])

    @unittest.skip
    def test_all_col_nums_finder(self):
        finder = AllColNumsFinder(df=self.duplicates_df)
        cell_values_results = [
            (
                CellValue('Общий итог'),
                [CellPosition(col=0), CellPosition(col=6)]
            ),
            (
                CellValue(2256),
                [CellPosition(col=1), CellPosition(col=6)]
            ),
            (
                CellValue(''),
                [
                    CellPosition(col=0),
                    CellPosition(col=1),
                    CellPosition(col=2),
                    CellPosition(col=3),
                    CellPosition(col=4),
                    CellPosition(col=5),
                    CellPosition(col=6),
                ]
            ),
            (
                CellValue(),
                [
                    CellPosition(col=0),
                    CellPosition(col=1),
                    CellPosition(col=2),
                    CellPosition(col=3),
                    CellPosition(col=4),
                    CellPosition(col=5),
                    CellPosition(col=6),
                ]
            ),
            (
                CellValue(0),
                [CellPosition(col=counter) for counter in range(2, 6)]
            ),
            (CellValue('Qwerty'), []),
            (CellValue(-999), [])
        ]
        for cell_value, expected_result_positions in cell_values_results:
            finder.value_finder = ExactValueFinder(cell_value=cell_value)
            fact_result_positions = finder.get_all_positions()
            if fact_result_positions:
                zip_result = zip_longest(fact_result_positions, expected_result_positions)
                for fact_position, expected_position in zip_result:
                    self.assertEqual(fact_position, expected_position)
            else:
                self.assertEqual(fact_result_positions, expected_result_positions)
                self.assertEqual(fact_result_positions, cell_values_results[-1][1])

    @unittest.skip
    def test_neighbor(self):
        data_set = [
            (CellPosition(col=0, row=0), CellValue(4302), CellOffset(col=1, row=1), True),
            (CellPosition(col=1, row=1), CellValue('SKU'), CellOffset(col=-1, row=-1), True),
            (CellPosition(col=1, row=1), CellValue(2012), CellOffset(col=0, row=-1), True),
            (CellPosition(col=1, row=1), CellValue(2013), CellOffset(col=1, row=-1), True),
            (CellPosition(col=1, row=1), CellValue(5987), CellOffset(col=1, row=0), True),
            (CellPosition(col=1, row=1), CellValue(3143), CellOffset(col=1, row=1), True),
            (CellPosition(col=1, row=1), CellValue(1099), CellOffset(col=0, row=1), True),
            (
                CellPosition(col=1, row=1),
                CellValue('*АРИТЕЛ ПЛЮС ТБ П/О 5МГ+6,25МГ №30'),
                CellOffset(col=-1, row=1),
                True
            ),
            (
                CellPosition(col=1, row=1),
                CellValue('*АРИТЕЛ ПЛЮС ТБ П/О 2,5МГ+6,25МГ №30'),
                CellOffset(col=-1, row=0),
                True
            ),
            (CellPosition(col=1, row=1), CellValue(4302), CellOffset(col=0, row=0), True),
            (CellPosition(col=0, row=0), CellValue(1), CellOffset(col=1, row=1), False),
        ]
        for cell_position, cell_value, cell_offset, result in data_set:
            neighbor = NeighborCell(df=self.simple_df, cell_value=cell_value, cell_offset=cell_offset)
            self.assertEqual(neighbor.is_neighbor(cell_position), result)

    @unittest.skip
    def test_cell_value(self):
        cell_values_numbers_results = [
            (CellValue(1), CellValue(1)),
            (CellValue(0), CellValue(0)),
            (CellValue(1.0), CellValue(1)),
            (CellValue(0.1), CellValue(0.1)),
            (CellValue(0.10), CellValue(0.1)),
            (CellValue(0.100), CellValue(0.1)),
            (CellValue(0.1000), CellValue(0.1)),
            (CellValue(0.10000), CellValue(0.1)),
            (CellValue(0.100000), CellValue(0.1)),
            (CellValue(0.1000000), CellValue(0.1)),
            (CellValue(0.10000000), CellValue(0.1)),
            (CellValue(0.100000000), CellValue(0.1)),
            (CellValue(0.1000000000), CellValue(0.1)),
            (CellValue(0.11), CellValue(0.11)),
            (CellValue(0.111), CellValue(0.111)),
            (CellValue(0.1111), CellValue(0.1111)),
            (CellValue(0.11111), CellValue(0.11111)),
            (CellValue(0.111111), CellValue(0.111111)),
            (CellValue(0.1111111), CellValue(0.1111111)),
            (CellValue(0.11111111), CellValue(0.11111111)),
            (CellValue(0.111111111), CellValue(0.111111111)),
            (CellValue(0.1111111111), CellValue(0.1111111111)),
            (CellValue(0.1111111111 + 0.1111111111), CellValue(0.2222222222)),
            (CellValue(1 + 1), CellValue(2)),
            (CellValue(1 + 0.11), CellValue(1.11)),
            (CellValue(1 + 0.111), CellValue(1.111)),
            (CellValue(1 + 0.1111), CellValue(1.1111)),
            (CellValue(1 + 0.11111), CellValue(1.11111)),
            (CellValue(1 + 0.111111), CellValue(1.111111)),
            (CellValue(1 + 0.1111111), CellValue(1.1111111)),
            (CellValue(1 + 0.11111111), CellValue(1.11111111)),
            (CellValue(1 + 0.111111111), CellValue(1.111111111)),
            (CellValue(1 + 0.1111111111), CellValue(1.1111111111)),
            (CellValue(1 + 0.1111111111 + 0.1111111111), CellValue(1.2222222222)),
            (CellValue(0.1 + 0.01), CellValue(0.11)),
            (CellValue(0.11 + 0.001), CellValue(0.111)),
            (CellValue(0.11 + 0.0011), CellValue(0.1111)),
            (CellValue(0.1 + 0.01111), CellValue(0.11111)),
            (CellValue(0.111 + 0.000111), CellValue(0.111111)),
            (CellValue(1 * 1.1), CellValue(1.1)),
            (CellValue(1.1 * 1.1), CellValue(1.2100000000000002)),
            (CellValue(1 / 3), CellValue(0.3333333333333333)),
            (CellValue(10.0/5.0), CellValue(2)),
        ]

        cell_values_strings_results = [
            (CellValue('Qwerty'), CellValue('Qwerty')),
            (CellValue('Qwerty '), CellValue('Qwerty ')),
            (CellValue(' Qwerty '), CellValue(' Qwerty ')),
            (CellValue(' Qwerty'), CellValue(' Qwerty')),
            (CellValue('123'), CellValue('123')),
            (CellValue('123A'), CellValue('123A')),
            (CellValue('A123A'), CellValue('A123A')),
            (CellValue('A123'), CellValue('A123')),
            (CellValue(''), CellValue('')),
            (CellValue(), CellValue('')),
            (CellValue(''), CellValue()),
            (CellValue('0'), CellValue('0')),
            (CellValue(' '), CellValue(' ')),
        ]

        for cell_value, result in cell_values_numbers_results:
            self.assertEqual(cell_value, result)
            self.assertEqual(CellValue(cell_value.value / 3), CellValue(result.value / 3))
            self.assertEqual(CellValue(-1 * cell_value.value), CellValue(-1 * result.value))
            self.assertEqual(CellValue(10 * cell_value.value), CellValue(10 * result.value))
            self.assertEqual(CellValue(-10 * cell_value.value), CellValue(-10 * result.value))

        for value in np.arange(0, 2, 0.3):
            self.assertEqual(CellValue(value), CellValue(value))
            self.assertEqual(CellValue(value * 0.1), CellValue(value * 0.1))
            self.assertEqual(CellValue(value / 3), CellValue(value / 3))
            self.assertEqual(CellValue(value + 10), CellValue(value + 10))
            self.assertEqual(CellValue(value - 999), CellValue(value - 999))

        for cell_value, result in cell_values_numbers_results:
            self.assertNotEqual(CellValue(-1 * cell_value.value + 1), CellValue(-1 * result.value))
            self.assertNotEqual(CellValue(-1 * cell_value.value + 0.1), CellValue(-1 * result.value))
            self.assertNotEqual(CellValue(10 * cell_value.value + 1), CellValue(10 * result.value))
            self.assertNotEqual(CellValue(10 * cell_value.value + 1.1), CellValue(10 * result.value))
            self.assertNotEqual(CellValue(-10 * cell_value.value + 1), CellValue(-10 * result.value))
            self.assertNotEqual(CellValue(-10 * cell_value.value + 1.1), CellValue(-10 * result.value))

        for cell_value, result in cell_values_strings_results:
            self.assertEqual(cell_value, result)
            str_val = str(cell_value)
            self.assertEqual(CellValue('' + str_val), CellValue('' + str_val))
            self.assertEqual(CellValue('' + str_val + ''), CellValue('' + str_val + ''))
            self.assertEqual(CellValue(str_val + ''), CellValue(str_val + ''))
            self.assertEqual(CellValue(str_val.replace('e', '\n')), CellValue(str_val.replace('e', '\n')))
            self.assertEqual(CellValue(str_val.replace('e', '\t')), CellValue(str_val.replace('e', '\t')))
            self.assertEqual(CellValue(str_val.replace('e', ' ')), CellValue(str_val.replace('e', ' ')))

        # Проверка оператора "in"
        cell_values = [
            CellValue(0),
            CellValue(0.1),
            CellValue(-0.1),
            CellValue(1),
            CellValue(-1),
            CellValue(1.1),
            CellValue(-1.1),
            CellValue('A'),
            CellValue(),
            CellValue(''),
        ]
        for ind in range(len(cell_values)):
            self.assertIn(cell_values[ind], cell_values)
        self.assertNotIn(CellValue(999), cell_values)
        self.assertNotIn(CellValue(-999), cell_values)
        self.assertNotIn(CellValue('Qwerty'), cell_values)

        self.assertTrue(CellValue('A'))
        self.assertTrue(CellValue(' '))
        self.assertTrue(CellValue(1))

        self.assertFalse(CellValue())
        self.assertFalse(CellValue(''))

        # TODO: datetime, datetime + timezone, time, 1E+4, 1.1 != '1.1'

    @unittest.skip
    def test_all_positions(self):
        cell_values_results = [
            (CellValue(4302), [CellPosition(col=1, row=1), CellPosition(col=1, row=3)]),
            (CellValue(2256), [CellPosition(col=1, row=8), CellPosition(col=6, row=8)]),
            (CellValue('Общий итог'), [CellPosition(col=6, row=0), CellPosition(col=0, row=11)]),
            (CellValue(-999), []),
            (CellValue('Qwerty'), []),
            (
                CellValue(''),
                [
                    CellPosition(col=6, row=1),
                    CellPosition(col=5, row=5),
                    CellPosition(col=0, row=12),
                    CellPosition(col=1, row=12),
                    CellPosition(col=2, row=12),
                    CellPosition(col=3, row=12),
                    CellPosition(col=4, row=12),
                    CellPosition(col=5, row=12),
                    CellPosition(col=6, row=12)
                ]
            ),
            (CellValue(0), [
                CellPosition(col=3, row=1),
                CellPosition(col=4, row=1),
                CellPosition(col=5, row=1),
                CellPosition(col=3, row=2),
                CellPosition(col=4, row=2),
                CellPosition(col=5, row=2),
                CellPosition(col=3, row=3),
                CellPosition(col=4, row=3),
                CellPosition(col=5, row=3),
                CellPosition(col=2, row=4),
                CellPosition(col=3, row=4),
                CellPosition(col=4, row=4),
                CellPosition(col=5, row=4),
                CellPosition(col=3, row=5),
                CellPosition(col=4, row=5),
                CellPosition(col=2, row=6),
                CellPosition(col=3, row=6),
                CellPosition(col=4, row=6),
                CellPosition(col=5, row=6),
                CellPosition(col=4, row=7),
                CellPosition(col=5, row=7),
                CellPosition(col=2, row=8),
                CellPosition(col=3, row=8),
                CellPosition(col=4, row=8),
                CellPosition(col=5, row=8),
                CellPosition(col=2, row=9),
                CellPosition(col=3, row=9),
                CellPosition(col=5, row=9),
                CellPosition(col=2, row=10),
                CellPosition(col=3, row=10),
                CellPosition(col=4, row=10),
                CellPosition(col=5, row=10),
            ])
        ]
        finder = AllCellPositionsFinder(df=self.duplicates_df)
        for cell_value, results in cell_values_results:
            finder.value_finder = ExactValueFinder(cell_value=cell_value)
            finder_results = finder.get_all_positions()
            self._check_results(expected_result=results, finder_result=finder_results)

    @unittest.skip
    def test_cell_values_finder(self):
        cell_values_results = [
            (
                [CellValue(3143), CellValue(2525)],
                [CellPosition(col=2, row=2), CellPosition(col=2, row=5), CellPosition(col=2, row=7)]
            ),
            (
                [CellValue('Общий итог'), CellValue()],
                [
                    CellPosition(col=6, row=0),
                    CellPosition(col=6, row=1),
                    CellPosition(col=5, row=5),
                    CellPosition(col=0, row=11),
                    CellPosition(col=0, row=12),
                    CellPosition(col=1, row=12),
                    CellPosition(col=2, row=12),
                    CellPosition(col=3, row=12),
                    CellPosition(col=4, row=12),
                    CellPosition(col=5, row=12),
                    CellPosition(col=6, row=12),
                ]
            ),

        ]
        finder = AllCellPositionsFinder(df=self.duplicates_df)
        for cell_values, results in cell_values_results:
            finder.value_finder = ExactValuesFinder(cell_values=cell_values)
            finder_results = finder.get_all_positions()
            self._check_results(expected_result=results, finder_result=finder_results)

    @unittest.skip
    def test_cell_value_finder_with_neighbors(self):
        cell_values_neighbors_results = [
            (
                CellValue(2525),
                [NeighborCell(df=self.duplicates_df, cell_value=CellValue('Общий итог'), cell_offset=CellOffset(col=-2, row=6))],
                [CellPosition(col=2, row=5)]
            ),
            (
                CellValue(0),
                [NeighborCell(df=self.duplicates_df, cell_value=CellValue(), cell_offset=CellOffset(col=0, row=-1))],
                [CellPosition(col=5, row=6)]
            ),
            (
                CellValue('Общий итог'),
                [NeighborCell(df=self.duplicates_df, cell_value=CellValue(), cell_offset=CellOffset(col=0, row=1))],
                [CellPosition(col=6, row=0), CellPosition(col=0, row=11)]
            ),
            (
                CellValue('Общий итог'),
                [NeighborCell(df=self.duplicates_df, cell_value=CellValue(''), cell_offset=CellOffset(col=0, row=1))],
                [CellPosition(col=6, row=0), CellPosition(col=0, row=11)]
            ),
            (
                CellValue('Общий итог'),
                [
                    NeighborCell(df=self.duplicates_df, cell_value=CellValue(), cell_offset=CellOffset(col=0, row=1)),
                    NeighborCell(df=self.duplicates_df, cell_value=CellValue(), cell_offset=CellOffset(col=1, row=1)),
                ],
                [CellPosition(col=0, row=11)]
            ),
            (
                CellValue('Общий итог'),
                [
                    NeighborCell(df=self.duplicates_df, cell_value=CellValue(), cell_offset=CellOffset(col=0, row=1)),
                    NeighborCell(df=self.duplicates_df, cell_value=CellValue(''), cell_offset=CellOffset(col=1, row=1)),
                ],
                [CellPosition(col=0, row=11)]
            ),
            (
                CellValue('Общий итог'),
                [
                    NeighborCell(df=self.duplicates_df, cell_value=CellValue(''), cell_offset=CellOffset(col=0, row=1)),
                    NeighborCell(df=self.duplicates_df, cell_value=CellValue(''), cell_offset=CellOffset(col=1, row=1)),
                ],
                [CellPosition(col=0, row=11)]
            ),
            (
                CellValue('Общий итог'),
                [
                    NeighborCell(df=self.duplicates_df, cell_value=CellValue(''), cell_offset=CellOffset(col=0, row=1)),
                    NeighborCell(df=self.duplicates_df, cell_value=CellValue(), cell_offset=CellOffset(col=1, row=1)),
                ],
                [CellPosition(col=0, row=11)]
            ),

        ]
        finder = AllCellPositionsFinder(df=self.duplicates_df)
        for cell_value, neighbors, results in cell_values_neighbors_results:
            finder.value_finder = ExactValueFinder(cell_value=cell_value)
            finder.neighbors_container = NeighborsContainer(neighbors=neighbors)
            finder_results = finder.get_all_positions()
            self._check_results(expected_result=results, finder_result=finder_results)

    @unittest.skip
    def test_cell_values_finder_with_neighbors(self):
        cell_values_neighbors_results = [
            (
                [CellValue(3143), CellValue(2525)],
                [NeighborCell(df=self.duplicates_df, cell_value=CellValue(4690), cell_offset=CellOffset(col=-1, row=0))],
                [CellPosition(col=2, row=5)]
            ),
        ]
        finder = AllCellPositionsFinder(df=self.duplicates_df)
        for cell_values, neighbors, results in cell_values_neighbors_results:
            finder.value_finder = ExactValuesFinder(cell_values=cell_values)
            finder.neighbors_container = NeighborsContainer(neighbors=neighbors)
            finder_results = finder.get_all_positions()
            self._check_results(expected_result=results, finder_result=finder_results)

    @unittest.skip
    def test_regex_cell_value_pattern(self):
        patterns_results = [
            (
                '.*КАНОН.*\d{4}.*',
                [CellPosition(col=0, row=5)]
            ),
            (
                '.*АРИТЕЛ ПЛЮС(?!.*2,5).*',
                [CellPosition(col=0, row=2), CellPosition(col=0, row=7), CellPosition(col=0, row=10)]
            ),
        ]
        finder = AllCellPositionsFinder(df=self.duplicates_df)
        for pattern, results in patterns_results:
            finder.value_finder = RegexFinder(cell_value=CellValue(pattern))
            finder_results = finder.get_all_positions()
            self._check_results(expected_result=results, finder_result=finder_results)

    @unittest.skip
    def test_cell_value_finder_with_offset(self):
        cell_values_results = [
            (CellValue('SKU'), CellOffset(col=1, row=0), [CellPosition(col=1, row=0)]),
            (CellValue('SKU'), CellOffset(col=-10, row=-10), []),
            (
                CellValue('Общий итог'),
                CellOffset(col=0, row=1),
                [
                    CellPosition(col=6, row=1),
                    CellPosition(col=0, row=12),
                ]
            ),
            (CellValue('Общий итог'), CellOffset(col=0, row=2), [CellPosition(col=6, row=2)]),
        ]
        finder = AllCellPositionsFinder(df=self.duplicates_df)
        for cell_value, cell_offset, results in cell_values_results:
            finder.value_finder = ExactValueFinder(cell_value=cell_value)
            finder.cell_offset_action = CellOffsetAction(cell_offset=cell_offset)
            finder_results = finder.get_all_positions()
            self._check_results(expected_result=results, finder_result=finder_results)

    @unittest.skip
    def test_startwith(self):
        patterns_results = [
            (
                '*',
                [
                    CellPosition(col=0, row=1), CellPosition(col=0, row=2), CellPosition(col=0, row=3),
                    CellPosition(col=0, row=4), CellPosition(col=0, row=5), CellPosition(col=0, row=6),
                    CellPosition(col=0, row=7), CellPosition(col=0, row=8), CellPosition(col=0, row=9),
                    CellPosition(col=0, row=10), CellPosition(col=0, row=11), CellPosition(col=0, row=12),
                    CellPosition(col=0, row=13), CellPosition(col=0, row=14), CellPosition(col=0, row=15),
                ]
            ),
            (
                'НЕБИВОЛОЛ-',
                [CellPosition(col=0, row=132), CellPosition(col=0, row=133)]
            ),
            (
                '102',
                []
            ),
            # TODO ДОбавить ещё проверки. Вариант котороый есть ив текстовой ячейке и числовой
        ]
        finder = AllCellPositionsFinder(df=self.simple_df)
        for value, results in patterns_results:
            finder.value_finder = StartWithFinder(cell_value=CellValue(value))
            finder_results = finder.get_all_positions()
            self._check_results(expected_result=results, finder_result=finder_results)

    @unittest.skip
    def test_endwith(self):
        patterns_results = [
            (
                '2,5МГ+6,25МГ №30',
                [CellPosition(col=0, row=1), CellPosition(col=0, row=3)]
            ),
            (
                '25',
                []
            ),
            # TODO ДОбавить ещё проверки. Вариант котороый есть ив текстовой ячейке и числовой
        ]
        finder = AllCellPositionsFinder(df=self.duplicates_df)
        for value, results in patterns_results:
            finder.value_finder = EndWithFinder(cell_value=CellValue(value))
            finder_results = finder.get_all_positions()
            self._check_results(expected_result=results, finder_result=finder_results)

    @unittest.skip
    def test_regex_finder_with_offset_neighbors(self):
        patterns_results = [
            (
                '^\*АРИТЕЛ.*',

            )
        ]

    def test_indexes(self):
        indexes_results = [
            ('a,', ['A']),
            ('a;', ['A']),
            (';a;', ['A']),
            (';a', ['A']),
            (',a', ['A']),
            (',a,', ['A']),
            ('a b C', ['A', 'B', 'C']),
            ('a B-e', ['A', 'B', 'C', 'D', 'E']),
            ('a B:    -e', ['A', 'B', 'C', 'D', 'E']),
            ('a B-   :e', ['A', 'B', 'C', 'D', 'E']),
            ('B:e a', ['B', 'C', 'D', 'E', 'A']),
            ('b:E ,, a', ['B', 'C', 'D', 'E', 'A']),
            ('b-:E ,; A', ['B', 'C', 'D', 'E', 'A']),
            ('b:E;A', ['B', 'C', 'D', 'E', 'A']),
            ('b-E A-c', ['B', 'C', 'D', 'E', 'A', 'B', 'C']),
            ('b-E A:c', ['B', 'C', 'D', 'E', 'A', 'B', 'C']),
            ('T b-E A:c T', ['T', 'B', 'C', 'D', 'E', 'A', 'B', 'C', 'T']),
            (' r,,, X  ----:-  -  Z t ;;; o', ['R', 'X', 'Y', 'Z', 'T', 'O']),
            ('a a a a a', ['A', 'A', 'A', 'A', 'A']),

            ('1 2 3', [1, 2, 3]),
            ('1;2,3', [1, 2, 3]),
            ('1;,;2,,3', [1, 2, 3]),
            ('1  -    3', [1, 2, 3]),
            ('1  -    3 6', [1, 2, 3, 6]),
            ('1-    3 6', [1, 2, 3, 6]),
            ('1   -3 6', [1, 2, 3, 6]),
            (' , 1  -    3 6 , ', [1, 2, 3, 6]),
            (' , 1  -:    3 6 ; ', [1, 2, 3, 6]),
            (' ,,, 1  ----:-  -  3 6 ;;; ', [1, 2, 3, 6]),
            (' 7,,, 1  ----:-  -  3 6 ;;; 9', [7, 1, 2, 3, 6, 9]),
            ('1 1 1 1 1', [1, 1, 1, 1, 1]),
        ]
        for value, result in indexes_results:
            try:
                indexes = Indexes(value)
            except:
                print(value, result)
            self.assertEqual(indexes.get_all_indexes(), result)

        bad_indexes = [
            'a b C, 1,2,3',
            'a b ,-F',
            'a b ;-F',
            'a b ,:F',
            'a b ;:F',
            'a b F-',
            'a b F-,',
            'a b F-;',
            'a b F-,G',
            'a b F,-G',
            'a b F:',
            'a b F:,',
            'a b F:;',
            'a b F:,G',
            'a b F,:G',
            'a b F:',
            'a b :F',
            ':',
            '-',
            ';',
            ' ',
            ',',
            ' : ',
            ' - ',
            ' ; ',
            '    ',
            ' , ',
            '1, 2, 2:f'
            '1, 2, 2-f'
            '1 2 ,-7',
            '1 2 ;-7',
            '1 2 ,:7',
            '1 2 ;:7',
            '1 2 7-',
            '1 2 7-,',
            '1 2 7-;',
            '1 2 7-,9',
            '1 2 7,-9',
            '1 2 7:',
            '1 2 7:,',
            '1 2 7:;',
            '1 2 7:,9',
            '1 2 7,:9',
            '1 2 7:',
            '1 2 :7',

            '4-1',
            'G-a',
            '1 111111111111',
            'a aaaaaaaaaaaa',
            'a ' * 200,
            '1 ' * 200,

        ]
        for value in bad_indexes:
            try:
                self.assertRaises(Exception, Indexes, indexes=value)
            except:
                print(value)

    def _check_results(self, expected_result: list, finder_result: list):
        self.assertEqual(len(expected_result), len(finder_result))
        for index, finder_cell_position in enumerate(finder_result):
            self.assertEqual(expected_result[index], finder_cell_position)


import time
import xbmc
import json
import xbmcgui
import xbmcaddon

import struct
import select
import os

def log(txt):
    message = '%s: %s' % ("service.rotdev", txt.encode('ascii', 'ignore'))
    xbmc.log(msg=message, level=xbmc.LOGINFO)

def getVolume():
    curVol = -1
    resp = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "Application.GetProperties", "params": { "properties": [ "volume"] }, "id": 1}')
    dct = json.loads(resp)
    if ("result" in dct and "volume" in dct["result"]):
        curVol = dct["result"]["volume"]
    return curVol

def getInputDevice():
    dev = -1
    for fn in os.listdir('/sys/class/input/'):
        if fn.startswith('event'):
            with open('/sys/class/input/' + fn + '/device/name') as f:
                if f.readline(8).startswith('rotary'):
                    dev = int(fn[5:])
                    break
    return dev

class Rotary:
    def __init__(self):
        self._vol = None
        self._device = None

    def __rotate(self, direction):
        vol = getVolume()
        if(direction == -1 and vol > 0):
            xbmc.executebuiltin("SetVolume({}, showVolumeBar)".format(vol-self._vol))
        elif(direction == 1 and vol < 100):
            xbmc.executebuiltin("SetVolume({}, showVolumeBar)".format(vol+self._vol))

    def setup(self):
        log("setup")
        self.stop()

        dev = getInputDevice()
        if dev == -1:
            log("No rotary device found")
            return

        _evdev = "/dev/input/event" + str(dev)

        _this = xbmcaddon.Addon()
        self._vol = int(_this.getSetting("vol_step"))

        log("config: %s %i" % (_evdev, self._vol))
        self._device = open(_evdev, "rb")

    def stop(self):
        log("stop")
        if self._device:
            self._device.close()
            self._device = None

    def read(self):
        if self._device:
            rrdy, wrdy, xrdy = select.select([self._device], [], [], 5);
            if rrdy:
                data = self._device.read(16)
                tmp = struct.unpack('2IHHi',data)
                if tmp[2] == 2:
                    self.__rotate(tmp[4])

class MyMonitor(xbmc.Monitor):
    def __init__(self, rotary):
        xbmc.Monitor.__init__(self)
        self.rotary = rotary

    def onSettingsChanged(self):
        log("onSettingsChanged")
        self.rotary.stop()
        self.rotary.setup()

if __name__ == '__main__':
    rot = Rotary()
    monitor = MyMonitor(rot)
    rot.setup()

    while not monitor.abortRequested():
        rot.read()

    rot.stop()


from bs4 import BeautifulSoup
import requests

def downloadDoi(url, filePath):
    '''
        Download a pdf file, corresponding to the paper from the doi link to filePath using sci-hub.
        Attributes:
            sci_hub_url: Base url of hte sci-hub site.
            headers: Headers for the request.
            r: Request of this sci-hub site to retrieve the paper link from and the pdf file.
            soup: BeautifulSoup object of the sci-hub page.
            embed: Find the element with the id of pdf from the page.
            link: Link if there is a pdf on the page.
            fd: File to write the pdf file to.
            chunk: Chunk of the pdf file to be saved to the disk.
        Arguments:
            url: Url of the doi of which we need to download the paper.
            filePath: Path we need to save the pdf to.
        Returns:
            True if a pdf file has been downloaded and saved to filePath, else False.
    '''
    sci_hub_url = 'https://sci-hub.se/'
    # Use headers, so act like we are a firefox browser:
    headers = {
        'User-Agent': 'Mozilla/5.0',
    }
    # Get the url, which consists of the sci_hub_url and then the doi url:
    url = sci_hub_url + url
    # Fetch this page and create a BeautifulSoup object:
    r = requests.get(url, headers=headers)
    soup = BeautifulSoup(r.content, 'html.parser')
    # Get the pdf embed elementL
    embed = soup.find(id='pdf')
    if embed:
        # If it exists, get the link:
        link = embed.get('src')
        if link:
            link = sci_hub_url + link
            # Retrieve this pdf file:
            r = requests.get(link, headers=headers)
            if r.status_code == 200:
                # If we have retrieved a pdf, save this to the filePath location:
                with open(filePath, 'wb') as fd:
                    for chunk in r.iter_content(chunk_size=128):
                        fd.write(chunk)
                # Return true, since we have written a file to the correct location:
                return True
    # Return false if we have not saved a file to the correct location:
    return False

def scrapePage(url):
    '''
        Scrape the page corresponding to the url and return the text.
        Attributes:
            headers: Headers for the request.
            r: Request of this sci-hub site to retrieve the paper link from and the pdf file.
            soup: BeautifulSoup object of the sci-hub page.
            text: Html text form this webpage.
            blacklist: Elements from the html page we do not want in the output.
            t: Elements from the html text we might remove or we add to the output.
        Arguments:
            url: Url we scrape the page from.
        Returns:
            True, if the page has successfully been scraped,
            together with output: Output of the text on the site corresponding to url.
            False, if the page could not be scraped, together with an error message.
    '''
    # Try to scrape the page:
    try:
        # Create the headers, which correspond to a firefox browser:
        headers = {
            'User-Agent': 'Mozilla/5.0',
        }
        # Retrieve the page corresponding to url:
        r = requests.get(url, headers=headers)
        # Create the BeautifulSoup object:
        soup = BeautifulSoup(r.content, 'html.parser')
        # Retrieve all text from this object:
        text = soup.find_all(string=True)
        # Create a output element:
        output = ''
        # Blacklist of HTML elements we do not want the text from:
        blacklist = [
            '[document]',
            'noscript',
            'style',
            'header',
            'html',
            'meta',
            'head',
            'input',
            'script',
            'link',
            'button',
            'form',
            'label',
            'amp-state',
            'footer',
            # there may be more elements you don't want, such as "style", etc.
        ]

        # For all object in the HTML source:
        for t in text:
            # If this element is not in the blacklist, add the text from this element to output:
            if t.parent.name not in blacklist:
                output += '{} '.format(t)
    # If this is unsuccessful, return the error message:
    except Exception as e:
        return False, str(e)
    # Else, return the output:
    return True, output


# -*- coding: utf-8 -*-
"""
Created on Sat Dec 19 18:12:31 2015

@author: Robert
"""

"""
Ce code permet de calculer la correction à l'énergie d'un niveau électronique donné (pour un réseau 2D),
en écrivant les intégrales I_2D(l1,p1) en coordonnées sphériques et en tirant les variables radiales
selon des lois gaussiennes et les variables angulaires uniformément.
L'invariance par translation dans la somme a été prise en compte.
"""

get_ipython().magic(u'pylab inline')
import numpy
import scipy
import math
from math import *
import random
import time

from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt

e2=2.3*math.pow(10,-28)
N=30
E0=13
t0=0.5
t=2
a=math.pow(10,-10)

l2=0
p2=0

#d : inférieur ou égal à a/4. pour une gaussienne (ainsi 1 % de recouvrement des gaussiennes
# de 2 orbitales atomiques localisée voisines en a/2)
d=a/4.
#Tester d'autres d ??

#Nombre d'ELECTRONS mis dans le système
#On remplit les états de façon non magnétique : en partant de l'état fondamental ;
#2 électrons (spins up et down) par état

#Si le nombre d'électrons est impair, on a forcément un état d'énergie avec un électron
#uniquement et donc un seul spin représenté. La correction calculée sera polarisée en spin.

#Nombre de valeurs de k_x occupées
alpha1=5
#Nombre de valeurs de k_y occupées
alpha2=10

NB=alpha1*alpha2
#NB maximal : 2*(N^2)

#Demi-remplissage : NB=N
#(N niveaux en tout ; 2 électrons par niveaux : donc 2N électrons au total au maximum)

k_x=[(-(2*pi/(N*a))*floor(N/2.)+i*(2*pi/(N*a))) for i in range(0,N)]

k_y=[(-(2*pi/(N*a))*floor(N/2.)+i*(2*pi/(N*a))) for i in range(0,N)]

def distance_reseau_2D(x,y,z,x1,y1,z1):
    #§Minimum entre 4 éléments
    var1=sqrt((x-(x1-N*a))**2+(y-y1)**2+(z-z1)**2)
    var2=sqrt((x-x1)**2+(y-(y1-N*a))**2+(z-z1)**2)
    var3=sqrt((x-(x1-N*a))**2+(y-(y1-N*a))**2+(z-z1)**2)
    return min(var1,var2,var3,sqrt((x-x1)**2+(y-y1)**2+(z-z1)**2))

def terme_facteur_phase(n_x,n_y,l1,p1):
    res=0

    for j_x in range(alpha1):
        for j_y in range(alpha2):
            if ((N-alpha1)/2+j_x!=n_x) or ((N-alpha2)/2+j_y!=n_y):

                res+= -cos((k_x[n_x]-k_x[(N-alpha1)/2+j_x])*(l1-l2)*a+(k_y[n_y]-k_y[(N-alpha2)/2+j_y])*(p1-p2)*a)

    if (n_x >= (N-alpha1)/2) and (n_x < (N+alpha1)/2) and (n_y >= (N-alpha2)/2) and (n_y < (N+alpha2)/2):
        return NB-1+res
    else:
        #(k_n_x,k_n_y) n'est pas un état occupé
        return NB+res

    #return res
    #Effet du terme de Fock seul

def F(l1,p1,r1,r2,theta1,theta2,phi1,phi2):
    #return (pi**2)*(2*pi)**2*(pi*(d/a)**2/2)*r1**2*r2**2*sin(theta1)*sin(theta2)/sqrt(r1**2+r2**2-2*r1*r2*(sin(theta1)*sin(theta2)*cos(phi1-phi2)+cos(theta1)*cos(theta2))+2*l1*(r2*sin(theta2)*cos(phi2)-r1*cos(phi1)*sin(theta1))+l1**2+2*p1*(r1*sin(theta1)*sin(phi1)-r2*sin(theta2)*sin(phi2))+p1**2)
    return r1**2*r2**2*sin(theta1)*sin(theta2)/distance_reseau_2D(r1*sin(theta1)*cos(phi1)-l1,r1*sin(theta1)*sin(phi1)-p1,r1*cos(phi1),r2*sin(theta2)*cos(phi2),r2*sin(theta2)*sin(phi2),r2*cos(phi2))
#Facteur exp(-2*r1**2/(d/a)**2)*exp(-2*r1**2/(d/a)**2) dans l'intégrande : contenu dans
#le tirage de r1 et r2 selon des gaussiennes
#Premier facteur : facteur de renormalisation de la densité de la gaussienne

nb=1000.

"""
r1_0=0.
r2_0=0.
theta1_0=0.
theta2_0=0.
phi1_0=0.
phi2_0=0.
"""

def I_2D(l1,p1):

    Y=[0 for i in range(6)]
    res=0

    for i in range(999):
        #Tirage d'un (i+1)ème uplet de variables aléatoires

        Y[0]=numpy.random.normal(0,(d/a)/2.)
        Y[1]=numpy.random.normal(0,(d/a)/2.)
        Y[2]=random.uniform(0,pi)
        Y[3]=random.uniform(0,pi)
        Y[4]=random.uniform(0,2*pi)
        Y[5]=random.uniform(0,2*pi)

        res+= F(l1,p1,Y[0],Y[1],Y[2],Y[3],Y[4],Y[5])

    return (pi**2/d)*(a/d)**3*(res/nb)
#Facteur K**4 * a**5 (où K est la partie angulaire de l'orbitale atomique)

"""
n=N/2
tab=[0 for i in range(N)]
for m_1 in range(N):
    tab[m_1]=I_1D_Metropolis(m_1)*terme_facteur_phase(n,m_1)
    #print("I_1D({0}) : {1} ".format(m_1,I_1D_Metropolis(m_1)))
    #print("Delta({2},{0}) : {1} ".format(m_1,terme_facteur_phase(n,m_1),n))
    print("I_1D({0})*Delta({2},{0}) : {1} ".format(m_1,I_1D_Metropolis(m_1)*terme_facteur_phase(n,m_1),n))

M=[i for i in range(N)]
plot(M,tab)
"""

"""
#Avec cette méthode, on recalcule pour chaque m
#I_1D(m) avec nb tirages du 6-uplet (rho1,rho2,theta1,theta2,phi1,phi2)
def Delta_Hartree_Fock(n_x,n_y):
    res2=0
    tab_I_2D=[[0 for i in range(N)] for j in range(N)]

    for l1 in range(N):
        for p1 in range(N):
            tab_I_2D[l1][p1]=I_2D(l1,p1)*terme_facteur_phase(n_x,n_y,l1,p1)
            res2+= tab_I_2D[l1][p1]
        #Le calcul de I_1D(m) nécessite à chaque m nb tirages...

    #abscisses_m=[i for i in range(N)]
    #plot(abscisses_m,tab_I_1D)
    return (e2/N**2)*res2

#print(Delta_Hartree_Fock(2))

nb_tirages=10000.

#Avec cette méthode, on évalue pour chaque tirage la valeur de l'intégrande de I_1D
#pour chaque m.
#Rajouter variable de spin en argument ?
def Delta_Hartree_Fock_bis(n_x,n_y):
    res=[[0 for i in range(N)] for j in range(N)]

    Y=[0 for i in range(6)]

    for i in range(9999):
        #Tirage d'un (i+1)ème uplet de variables aléatoires
        Y[0]=numpy.random.normal(0,(d/a)/sqrt(2.))
        Y[1]=numpy.random.normal(0,(d/a)/sqrt(2.))
        Y[2]=random.uniform(0,pi)
        Y[3]=random.uniform(0,pi)
        Y[4]=random.uniform(-pi,pi)
        Y[5]=random.uniform(-pi,pi)

        #tab=[0 for i in range(N)]

        for l1 in range(N):
            for p1 in range(N):
                res[l1][p1] += F(l1,p1,Y[0],Y[1],Y[2],Y[3],Y[4],Y[5])
                #tab[m] = F(m,p1,Y[0],Y[1],Y[2],Y[3],Y[4],Y[5])

        #Vérification que à p1 fixé, pour un tirage des variables donné, l1 -> F(l1,p1,Y[0],Y[1],Y[2],Y[3],Y[4],Y[5]) évolue bien en 1/|l1|
        #abscisses_m=[i for i in range(10)]
        #print(Y)
        #print(F(1,1,Y[0],Y[1],Y[2],Y[3],Y[4],Y[5]))
        #plot(abscisses_m,tab[0:10])

    I_2D_estime=[[0 for i in range(N)] for j in range(N)]

    for l1 in range(N):
            for p1 in range(N):
                I_2D_estime[l1][p1]=res[l1][p1]/nb_tirages

    resultat_correction_energie=0
    for l_1 in range(N):
        for p_1 in range(N):
            resultat_correction_energie += I_2D_estime[l1][p1]*terme_facteur_phase(n_x,n_y,l_1,p_1)

    return (e2/N**2)*resultat_correction_energie
"""

"""
n=5

#Estimation de l'écart type pour un nombre de tirages donné

#Nombre de calculs de la correction (=nb de fois que l'on a simulé une chaîne de Markov)
p=10.

#Estimateur de la moyenne
mu=0.

#Somme des réalisations
sum=0.

#Estimteur de la moyenne du carré
m=0.

#Somme des carrés des réalisations
sum2=0.

#Estimateur de l'écart type
sigma=0.

for i in range(10):

    tps1=time.clock()
    var =Delta_Hartree_Fock_bis(n)
    tps2=time.clock()
    sum +=var
    sum2+=var**2
    print("Delta_Hartree_Fock_bis({0}) : {1}  Temps calcul : {2} ".format(n,var,tps2-tps1))

mu=sum/p
m=sum2/p
sigma=sqrt(m-mu**2)
print("Ecart-type : {0}  Moyenne : {1}".format(sigma,mu))
print("Pourcentage : {0} %".format(100*sigma/mu))
"""

"""
tab=[[0 for i in range(N)] for j in range(N)]
for m in range(N):
    for q in range(N):
        tab[m][q]=Delta_Hartree_Fock_bis(m,q)
        print("Delta_Fock_w_s_i_(k_x[{0}]={1},k_y[{2}]={3}) : {4} ".format(m,k_x[m],q,k_y[q],tab[m][q]))
"""

#numpy.savetxt('Correction_energie_2D_bis.txt',tab,newline='\n')

valeurs_I_2D=[[0 for i in range(N)] for j in range(N)]
#On calcule les valeurs de I_1D une fois pour toutes (en faisant beaucoup de tirages pour un m donné et ainsi avoir peu d'incertitudes)
#(elles sont indépendantes de n ; n n'intervient que dans le facteur de phase)
for l1 in range(N):
    for p1 in range(N):
        valeurs_I_2D[l1][p1]=I_2D(l1,p1)

#print(valeurs_I_2D)

null=linspace(0.,0.,N)
correction_energie=np.outer(null,np.ones(np.size(null)))

for n_x in range(N):
    for n_y in range(N):
        #tps1=time.clock()

        res=0
        for l1 in range(N):
            for p1 in range(N):
                #print("terme_facteur_phase({0},{1}) = {2}".format(v,u,terme_facteur_phase(v,u)))
                res+= valeurs_I_2D[l1][p1]*terme_facteur_phase(n_x,n_y,l1,p1)

        correction_energie[n_x][n_y]=(1/(1.6*math.pow(10,-19)))*(e2/N**2)*res

        #tps2=time.clock()
        #print("Delta_Hartree_Fock({0},{1})= {2}  Temps calcul : {3}".format(n_x,n_y,correction_energie[n_x][n_y],tps2-tps1))

#Graphe en 3D de la correction de l'énergie par le terme de Hartree Fock
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d') #Créaton d'axes 3D

#plt.show()

k_x_1=np.outer(k_x,np.ones(np.size(k_x)))

k_y_1=np.outer(np.ones(np.size(k_y)),k_y)

energie_2D_corrigee=[[(E0-t0-2*t*(cos(k_x[i]*a)+cos(k_y[j]*a))+correction_energie[i][j]) for i in range(N)] for j in range(N)]
#energie_corrigee=[[(energie_2D[i][j],correction_energie[i][j]) for i in range(N)] for j in range(N)]
#energie_corrigee=[[(energie_2D[i][j]+correction_energie[i][j]) for i in range(N)] for j in range(N)]

ax.plot_surface(k_x_1, k_y_1,correction_energie,rstride=1, cstride=1, cmap=plt.cm.coolwarm, linewidth=0.05, antialiased=True,alpha=1)
#ax.view_init(elev=10,azim=0)
#La fonction plot_surface trace à partir de 3 matrices A, B et C, l'ensemble des points
#de coordonnées (A[i][j], B[i][j], C[i][j]) et les relie pour former une surface.
plt.title("Correction given by the Hartree-Fock term for the 2D lattice,  in eV", fontsize=10)
ylabel("kx (m^(-1))")
xlabel("ky (m^(-1))")
plt.show()
plt.hold()
#ax.contour(k_x_1, k_y_1, energie_corrigee,zdir='z')

#ax.set_zlim(E0-t0-4*t, E0-t0+4*t)

ax.zaxis.set_major_locator(plt.LinearLocator(10))
ax.zaxis.set_major_formatter(plt.FormatStrFormatter('%.02f'))

X,Y= meshgrid(k_x,k_y)
pcolor(X,Y,correction_energie)
xlabel("kx (m^(-1))")
ylabel("ky (m^(-1))")
show()

#fig.colorbar(surf, shrink=0.5, aspect=10)

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d') #Créaton d'axes 3D

ax.plot_surface(k_x_1, k_y_1,energie_2D_corrigee,rstride=1, cstride=1, cmap=plt.cm.coolwarm, linewidth=0.05, antialiased=True,alpha=1)
plt.title("Energy corrected by the Hartree-Fock term for the 2D lattice (eV)", fontsize=10)
xlabel("kx (m^(-1))")
ylabel("ky (m^(-1))")
#ax.view_init(elev=10,azim=40)

plt.show()
plt.hold()

"""
X,Y= meshgrid(k_x,k_y)
pcolor(X,Y,energie_2D)
show()
"""