import os
import numpy as np
import nibabel as nib
from sklearn.utils import shuffle
from tqdm import tqdm
from .pad import *
import random
import copy
from time import strftime, time

import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import sys

def PadImage(vol, padsize):
    dim = vol.shape
    padsize = np.asarray(padsize, dtype=int)
    dim2 = dim+2*padsize
    temp = np.zeros(dim2, dtype=float)
    temp[padsize:dim[0]+padsize,
         padsize:dim[1]+padsize,
         padsize:dim[2]+padsize] = vol
    return temp

def get_intersection(a, b):
    '''
    Gets intersection of coordinate arrays a and b returned from np.nonzero()-style functions.
    Used to find valid centers for healthy patches.

    TODO: CURRENTLY ONLY WORKS ON 3D ARRAYS

    Params:
        - a: tuple of N np.arrays, where N is the dimension of the original image
        - b: tuple of N np.arrays, where N is the dimension of the original image
    Returns:
        - intersection: set of tuples of rank N, where N is the dimension of the original image
    '''
    # TODO: this is the slowest operation
    start_time = time()
    print(np.array(a).shape)
    print(np.array(b).shape)

    if len(a) == 3:
        # first change format to be a list of coordinates rather than one list per dimension
        a_reformat = [(x, y, z) for x, y, z in zip(a[0], a[1], a[2])]
        b_reformat = [(x, y, z) for x, y, z in zip(b[0], b[1], b[2])]
    elif len(a) == 2:
        a_reformat = [(x, y) for x, y in zip(a[0], a[1])]
        b_reformat = [(x, y) for x, y in zip(b[0], b[1])]
    else:  # TODO: proper error handling
        print("Shape mismatch")

    intersection = set(a_reformat) & set(b_reformat)
    print("Time taken to calculate intersection:",
          time() - start_time, "seconds")

    return intersection

def get_center_coords(ct, mask, ratio):
    '''
    Gets coordinates for center pixel of all patches.

    Params:
        - ct: 3D ndarray, image data from which to find healthy coordinates
        - mask: 3D ndarray, image data from which to find coordinates
        - ratio: float in [0,1].
                 If 0 or 1, skip intersection calculation for speed
    Returns:
        - healthy_coords: set of tuples of rank 3, coordinates of healthy voxels
        - lesion_coords: set of tuples of rank 3, coordinates of lesion voxels
    '''

    # These first two must be shuffled.
    if ratio == 1:
        # ct-valid patches
        ct_possible_centers = np.nonzero(ct)
        healthy_coords = [(x, y, z) for x, y, z in zip(ct_possible_centers[0],
                                                       ct_possible_centers[1],
                                                       ct_possible_centers[2])]
        healthy_coords = set(shuffle(healthy_coords, random_state=0))
        lesion_coords = {(0, 0, 0), (0, 0, 0), (0, 0, 0), }
    elif ratio == 0:
        healthy_coords = {(0, 0, 0), (0, 0, 0), (0, 0, 0), }
        # mask lesion patches
        lesion_coords = np.nonzero(mask)
        # cuurently only works for 3D input images
        lesion_coords = [(x, y, z) for x, y, z in zip(lesion_coords[0],
                                                      lesion_coords[1],
                                                      lesion_coords[2])]
        lesion_coords = set(shuffle(lesion_coords, random_state=0))
    else:
        # ct-valid patches
        ct_possible_centers = np.nonzero(ct)
        zeros_coords = np.where(mask == 0)
        healthy_coords = get_intersection(ct_possible_centers, zeros_coords)
        # mask lesion patches
        lesion_coords = np.nonzero(mask)
        # currently only works for 3D input images
        # This does not need to be shuffled since it will be shuffled later
        lesion_coords = set([(x, y, z) for x, y, z in zip(lesion_coords[0],
                                                          lesion_coords[1],
                                                          lesion_coords[2])])

    return healthy_coords, lesion_coords

def get_patches(invols, mask, patchsize, maxpatch, num_channels):
    rng = random.SystemRandom()

    mask = np.asarray(mask, dtype=np.float32)
    patch_size = np.asarray(patchsize, dtype=int)
    dsize = np.floor(patch_size/2).astype(dtype=int)

    # find indices of all lesions in mask volume
    mask_lesion_indices = np.nonzero(mask)
    mask_lesion_indices = np.asarray(mask_lesion_indices, dtype=int)
    total_lesion_patches = len(mask_lesion_indices[0])

    num_patches = np.minimum(maxpatch, total_lesion_patches)
    '''
    print("Number of patches used: {} out of {} (max: {})"
          .format(num_patches,
                  total_lesion_patches,
                  maxpatch))
    '''

    randidx = rng.sample(range(0, total_lesion_patches), num_patches)
    # here, 3 corresponds to each axis of the 3D volume
    shuffled_mask_lesion_indices = np.ndarray((3, num_patches))
    for i in range(0, num_patches):
        for j in range(0, 3):
            shuffled_mask_lesion_indices[j,
                                         i] = mask_lesion_indices[j, randidx[i]]
    shuffled_mask_lesion_indices = np.asarray(
        shuffled_mask_lesion_indices, dtype=int)

    # mask out all lesion indices to get all healthy indices
    tmp = copy.deepcopy(invols[0])
    tmp[tmp > 0] = 1
    tmp[tmp <= 0] = 0
    tmp = np.multiply(tmp, 1-mask)

    healthy_brain_indices = np.nonzero(tmp)
    healthy_brain_indices = np.asarray(healthy_brain_indices, dtype=int)
    num_healthy_indices = len(healthy_brain_indices[0])

    randidx0 = rng.sample(range(0, num_healthy_indices), num_patches)
    # here, 3 corresponds to each axis of the 3D volume
    shuffled_healthy_brain_indices = np.ndarray((3, num_patches))
    for i in range(0, num_patches):
        for j in range(0, 3):
            shuffled_healthy_brain_indices[j,
                                           i] = healthy_brain_indices[j, randidx0[i]]
    shuffled_healthy_brain_indices = np.asarray(
        shuffled_healthy_brain_indices, dtype=int)

    newidx = np.concatenate([shuffled_mask_lesion_indices,
                             shuffled_healthy_brain_indices], axis=1)

    CT_matsize = (2*num_patches, patchsize[0], patchsize[1], num_channels)
    Mask_matsize = (2*num_patches, patchsize[0], patchsize[1], 1)

    #CTPatches = np.ndarray(CT_matsize, dtype=np.float16)
    #MaskPatches = np.ndarray(Mask_matsize, dtype=np.float16)

    CTPatches = []
    MaskPatches = []

    for i in range(0, 2*num_patches):
        I = newidx[0, i]
        J = newidx[1, i]
        K = newidx[2, i]

        if I - dsize[0] < 0 or\
                I + dsize[0] > invols[0].shape[0] or\
                J - dsize[1] < 0 or\
                J + dsize[1] > invols[0].shape[1]:
                    continue

        if np.sum(
            invols[0][I - dsize[0]: I + dsize[0],
                      J - dsize[1]: J + dsize[1],
                      K]
                ) == 0:
            continue

        for c in range(num_channels):
            CTPatches.append(invols[c][I - dsize[0]: I + dsize[0],
                                              J - dsize[1]: J + dsize[1],
                                              K])
        MaskPatches.append(mask[I - dsize[0]: I + dsize[0],
                                       J - dsize[1]:J + dsize[1],
                                       K])

    #CTPatches = np.asarray(CTPatches, dtype=np.float16)
    #MaskPatches = np.asarray(MaskPatches, dtype=np.float16)

    return CTPatches, MaskPatches

def get_slices(img_vol, target_size):
    num_z_steps = img_vol.shape[2]

    slices = []
    for z in range(num_z_steps):
        sl = img_vol[:, :, z]
        sl = pad_crop_image_2D(sl, target_size)

        if np.sum(sl) > 0:
            slices.append(sl)

    slices = np.array(slices)
    slices = np.reshape(slices, slices.shape + (1,))

    return slices

def get_nonoverlapping_patches(img_vol, patch_size):
    '''
    Gets all 2D non-overlapping, non-zero patches of `patchsize` from `img`
    '''
    num_x_steps = img_vol.shape[0] // patch_size[0]
    num_y_steps = img_vol.shape[1] // patch_size[1]
    num_z_steps = img_vol.shape[2]

    patches = []
    for z in range(num_z_steps):
        for x in range(0, img_vol.shape[0] - patch_size[0] + 1, patch_size[0]):
            for y in range(0, img_vol.shape[1] - patch_size[1] + 1, patch_size[1]):
                patch = img_vol[
                    x:x+patch_size[0],
                    y:y+patch_size[1],
                    z
                ]

                if np.sum(patch) > 0:
                    patches.append(patch)

    patches = np.array(patches)
    if len(patches.shape) != 3:
        return np.zeros((1, *patch_size))
    patches = np.rollaxis(patches, 2, -1)
    patches = np.reshape(patches, patches.shape + (1,))

    return patches

def CreatePatchesForTraining(atlasdir, plane, patchsize, max_patch=150000, num_channels=1):
    '''

    Params:
        - TODO
        - healthy: bool, False if not going over the healthy dataset, true otherwise

    '''

    # get filenames
    ct_names = os.listdir(atlasdir)
    mask_names = os.listdir(atlasdir)

    ct_names = [x for x in ct_names if "CT" in x]
    mask_names = [x for x in mask_names if "mask" in x]

    ct_names.sort()
    mask_names.sort()

    numatlas = len(ct_names)

    patchsize = np.asarray(patchsize, dtype=int)
    padsize = np.max(patchsize + 1)  # / 2

    # calculate total number of voxels for all images to pre-allocate array
    f = 0
    for i in range(0, numatlas):
        maskname = mask_names[i]
        maskname = os.path.join(atlasdir, maskname)
        temp = nib.load(maskname)
        mask = temp.get_data()
        f = f + np.sum(mask)

    print("Total number of lesion patches =", f)
    total_num_patches = int(np.minimum(max_patch * numatlas, f))
    single_subject_num_patches = total_num_patches // numatlas
    print("Allowed total number of patches =", total_num_patches)

    # note here we double the size of the tensors to allow for healthy patches too
    doubled_num_patches = total_num_patches * 2
    if plane == "axial":
        CT_matsize = (doubled_num_patches,
                      patchsize[0], patchsize[1], num_channels)
        Mask_matsize = (doubled_num_patches, patchsize[0], patchsize[1], 1)
    elif plane == "sagittal":
        CT_matsize = (doubled_num_patches,
                      patchsize[0], 16, num_channels)
        Mask_matsize = (doubled_num_patches, patchsize[0], 16, 1)
    elif plane == "coronal":
        CT_matsize = (doubled_num_patches,
                      16, patchsize[1], num_channels)
        Mask_matsize = (doubled_num_patches, 16, patchsize[1], 1)

    CTPatches = np.zeros(CT_matsize, dtype=np.float16)
    MaskPatches = np.zeros(Mask_matsize, dtype=np.float16)

    indices = [x for x in range(doubled_num_patches)]
    indices = shuffle(indices, random_state=0)
    cur_idx = 0

    # interpret plane
    planar_codes = {"axial": (0, 1, 2),
                    "sagittal": (1, 2, 0),
                    "coronal": (2, 0, 1)}
    planar_code = planar_codes[plane]

    for i in tqdm(range(0, numatlas)):
        ctname = ct_names[i]
        ctname = os.path.join(atlasdir, ctname)

        temp = nib.load(ctname)
        ct = temp.get_data()
        ct = np.asarray(ct, dtype=np.float16)

        maskname = mask_names[i]
        maskname = os.path.join(atlasdir, maskname)
        temp = nib.load(maskname)
        mask = temp.get_data()
        mask = np.asarray(mask, dtype=np.float16)

        # here, need to ensure that the CT and mask tensors
        # are padded out to larger than the size of the requested
        # patches, to allow for patches to be gathered from edges
        ct = PadImage(ct, padsize)
        mask = PadImage(mask, padsize)

        ct = np.transpose(ct, axes=planar_code)
        mask = np.transpose(mask, axes=planar_code)

        invols = [ct]  # can handle multichannel here

        # adjusting patch size after transpose
        if planar_code != planar_codes["axial"]:
            if ct.shape[0] < ct.shape[1]:
                patchsize = (ct.shape[0]//4, patchsize[1])
            if ct.shape[1] < ct.shape[0]:
                patchsize = (patchsize[0], ct.shape[1]//4)
        patchsize = np.asarray(patchsize, dtype=int)

        CTPatchesA, MaskPatchesA = get_patches(invols,
                                               mask,
                                               patchsize,
                                               single_subject_num_patches,
                                               num_channels,)

        CTPatchesA = np.asarray(CTPatchesA, dtype=np.float16)
        MaskPatchesA = np.asarray(MaskPatchesA, dtype=np.float16)

        for ct_patch, mask_patch in zip(CTPatchesA, MaskPatchesA):
            CTPatches[indices[cur_idx], :, :, :] = ct_patch
            MaskPatches[indices[cur_idx], :, :, :] = mask_patch
            cur_idx += 1

    return (CTPatches, MaskPatches)


# -*- coding: utf-8 -*-

from selenium import webdriver
import time

def capture(url, img_file="test1.png"):
    safari = webdriver.Safari()
    safari.set_window_size(1200, 900)
    safari.get(url)
    safari.execute_script("""
        (function () {
            var y = 0;
            var step = 100;
            window.scroll(0, 0);

            function f() {
                if (y < document.body.scrollHeight) {
                    y += step;
                    window.scroll(0, y);
                    setTimeout(f, 50);
                } else {
                    window.scroll(0, 0);
                    document.title += "scroll-done";
                }
            }

            setTimeout(f, 1000);
        })();
    """)

    for i in xrange(30):
        if "scroll-done" in safari.title:
            break
        time.sleep(1)

    safari.save_screenshot(img_file)
    safari.close()

if __name__ == "__main__":
    capture("shijinianq")


from msrest.serialization import Model

class CertificateRequestRecordApiModel(Model):
    """Certificate request record model.

    :param request_id: Request id
    :type request_id: str
    :param entity_id: Application id
    :type entity_id: str
    :param group_id: Trust group
    :type group_id: str
    :param state: Request state. Possible values include: 'New', 'Approved',
     'Rejected', 'Failure', 'Completed', 'Accepted'
    :type state: str or ~azure-iiot-opc-vault.models.CertificateRequestState
    :param type: Request type. Possible values include: 'SigningRequest',
     'KeyPairRequest'
    :type type: str or ~azure-iiot-opc-vault.models.CertificateRequestType
    :param error_info: Error diagnostics
    :type error_info: object
    :param submitted: Request time
    :type submitted:
     ~azure-iiot-opc-vault.models.VaultOperationContextApiModel
    :param approved: Approved or rejected
    :type approved: ~azure-iiot-opc-vault.models.VaultOperationContextApiModel
    :param accepted: Finished
    :type accepted: ~azure-iiot-opc-vault.models.VaultOperationContextApiModel
    """

    _attribute_map = {
        'request_id': {'key': 'requestId', 'type': 'str'},
        'entity_id': {'key': 'entityId', 'type': 'str'},
        'group_id': {'key': 'groupId', 'type': 'str'},
        'state': {'key': 'state', 'type': 'CertificateRequestState'},
        'type': {'key': 'type', 'type': 'CertificateRequestType'},
        'error_info': {'key': 'errorInfo', 'type': 'object'},
        'submitted': {'key': 'submitted', 'type': 'VaultOperationContextApiModel'},
        'approved': {'key': 'approved', 'type': 'VaultOperationContextApiModel'},
        'accepted': {'key': 'accepted', 'type': 'VaultOperationContextApiModel'},
    }

    def __init__(self, request_id=None, entity_id=None, group_id=None, state=None, type=None, error_info=None, submitted=None, approved=None, accepted=None):
        super(CertificateRequestRecordApiModel, self).__init__()
        self.request_id = request_id
        self.entity_id = entity_id
        self.group_id = group_id
        self.state = state
        self.type = type
        self.error_info = error_info
        self.submitted = submitted
        self.approved = approved
        self.accepted = accepted


# Define here the models for your scraped items
#
# See documentation in:
# http://doc.scrapy.org/topics/items.html

from scrapy.item import Item, Field

class MongoItem(Item):
    _mongo_keys = []
    def __init__(self, *args, **kwargs):
        super(MongoItem, self).__init__(*args, **kwargs)
        for k in self._mongo_keys:
            self.fields[k] = {}

    def get_mongo_obj(self):
        it = {}
        for k in self._mongo_keys:
            it[k] = self.get(k)
        return it

class TimewarnerAccount(Item):
    account_id = Field()

class TimewarnerCurrent(MongoItem):
    _collection_name = "timewarner_current"
    _mongo_keys = [
            "startdate",
            "enddate",
            "cloud_account_id",
            "account_id",
            "total",
            "tax"
            ]

class TimewarnerData(MongoItem):
    _collection_name = "timewarner_data"
    _mongo_keys = [
            "cloud_account_id",
            "account_id",
            "startdate",
            "enddate",
            "total",
            "services"
            ]


import numpy as np
import tensorflow as tf
import math
import librosa
from kapre import STFT, Magnitude, MagnitudeToDecibel
from tensorflow.keras.layers import (
    Conv1D,
    MaxPool1D,
    BatchNormalization,
    GlobalAvgPool1D,
    Multiply,
    GlobalMaxPool1D,
    Dense,
    Dropout,
    Activation,
    Reshape,
    Input,
    Concatenate,
    Add,
    ZeroPadding1D,
    LeakyReLU,
)
from tensorflow.keras.regularizers import l2
from tensorflow.keras.models import Model
from tensorflow.keras import backend as K
from tensorflow.keras.layers import Layer
from tensorflow.python.keras.utils import conv_utils

from keras import initializers

def hz_to_midi(hz):
    return 12 * (torch.log2(hz) - np.log2(440.0)) + 69

def midi_to_hz(midi):
    return 440.0 * (2.0 ** ((midi - 69.0) / 12.0))

def note_to_hz(note):
    return librosa.core.note_to_hz(note)

def note_to_midi(note):
    return librosa.core.note_to_midi(note)

def hz_to_note(hz):
    return librosa.core.hz_to_note(hz)

def initialize_filterbank(sample_rate, n_harmonic, semitone_scale):
    # MIDI
    # lowest note
    low_midi = note_to_midi("C1")

    # highest note
    high_note = hz_to_note(sample_rate / (2 * n_harmonic))
    high_midi = note_to_midi(high_note)

    # number of scales
    level = (high_midi - low_midi) * semitone_scale
    midi = np.linspace(low_midi, high_midi, level + 1)
    # midi = tf.cast(midi, tf.float32)
    hz = midi_to_hz(midi[:-1])

    # stack harmonics
    harmonic_hz = []
    for i in range(n_harmonic):
        harmonic_hz = np.concatenate((harmonic_hz, hz * (i + 1)))

    return harmonic_hz, level

class HarmonicSTFT(tf.keras.layers.Layer):
    def __init__(
        self,
        sample_rate=16000,
        n_fft=512,
        win_length=None,
        hop_length=None,
        pad=0,
        power=2,
        normalized=False,
        n_harmonic=6,
        semitone_scale=2,
        bw_Q=1.0,
        learn_bw=None,
    ):
        super(HarmonicSTFT, self).__init__()

        # Parameters
        self.sample_rate = sample_rate
        self.n_harmonic = n_harmonic
        self.bw_alpha = 0.1079
        self.bw_beta = 24.7
        self.win_length = win_length
        self.hop_length = hop_length
        self.n_fft = n_fft
        self.fft_bins = tf.linspace(0, self.sample_rate // 2, self.n_fft // 2 + 1)
        self.stft = STFT(
            n_fft=self.n_fft,
            win_length=self.win_length,
            hop_length=256,
            window_name="hann_window",
            input_shape=(80000, 1),
            pad_begin=True,
        )
        self.magnitude = Magnitude()
        self.to_decibel = MagnitudeToDecibel()
        self.zero = tf.zeros(
            [
                1,
            ]
        )
        # Spectrogram

        # Initialize the filterbank. Equally spaced in MIDI scale.
        harmonic_hz, self.level = initialize_filterbank(
            sample_rate, n_harmonic, semitone_scale
        )

        # Center frequncies to tensor
        self.f0 = tf.constant(harmonic_hz, dtype="float32")
        # Bandwidth parameters
        self.bw_Q = tf.Variable(np.array([bw_Q]), dtype="float32", trainable=True)

    def get_harmonic_fb(self):
        # bandwidth
        bw = (self.bw_alpha * self.f0 + self.bw_beta) / self.bw_Q
        bw = tf.dtypes.cast(tf.expand_dims(bw, axis=0), dtype=tf.float32)
        f0 = tf.dtypes.cast(tf.expand_dims(self.f0, axis=0), dtype=tf.float32)
        fft_bins = tf.dtypes.cast(
            tf.expand_dims(self.fft_bins, axis=1), dtype=tf.float32
        )

        up_slope = tf.matmul(fft_bins, (2 / bw)) + 1 - (2 * f0 / bw)
        down_slope = tf.matmul(fft_bins, (-2 / bw)) + 1 + (2 * f0 / bw)
        fb = tf.math.maximum(self.zero, tf.math.minimum(down_slope, up_slope))
        return fb

        # Scale magnitude relative to maximum value in S. Zeros in the output
        # correspond to positions where S == ref.
        ref = tf.reduce_max(S)

        log_spec = 10.0 * _tf_log10(tf.maximum(amin, S))
        log_spec -= 10.0 * _tf_log10(tf.maximum(amin, ref))

        log_spec = tf.maximum(log_spec, tf.reduce_max(log_spec) - top_db)

        return log_spec

    def call(self, input_tensor, training=False):
        # 80000, 1
        waveform = tf.keras.layers.Reshape((-1, 1))(input_tensor)

        # stft
        spec = self.stft(waveform)
        spec = self.magnitude(spec)

        harmonic_fb = self.get_harmonic_fb()
        harmonic_fb = tf.expand_dims(harmonic_fb, axis=0)
        harmonic_spec = tf.matmul(tf.transpose(spec, perm=[0, 3, 1, 2]), harmonic_fb)
        b, c, w, h = harmonic_spec.shape
        harmonic_spec = tf.keras.layers.Reshape(
            (-1, h // self.n_harmonic, self.n_harmonic)
        )(harmonic_spec)
        harmonic_spec = tf.transpose(harmonic_spec, perm=[0, 2, 1, 3])
        harmonic_spec = self.to_decibel(harmonic_spec)
        return harmonic_spec

class ResNet_mtat(tf.keras.layers.Layer):
    def __init__(self, input_channels, conv_channels=128):
        super(ResNet_mtat, self).__init__()
        self.num_class = 50

        # residual convolution
        self.res1 = Conv3_2d(conv_channels, 2)
        self.res2 = Conv3_2d_resmp(conv_channels, 2)
        self.res3 = Conv3_2d_resmp(conv_channels, 2)
        self.res4 = Conv3_2d_resmp(conv_channels, 2)
        self.res5 = Conv3_2d(conv_channels * 2, 2)
        self.res6 = Conv3_2d_resmp(conv_channels * 2, (2, 3))
        self.res7 = Conv3_2d_resmp(conv_channels * 2, (2, 3))

        # fully connected
        self.fc_1 = tf.keras.layers.Dense(conv_channels * 2)
        self.bn = tf.keras.layers.BatchNormalization()
        self.fc_2 = tf.keras.layers.Dense(self.num_class)
        self.activation = tf.keras.layers.Activation("sigmoid")
        self.dropout = tf.keras.layers.Dropout(0.5)
        self.relu = tf.keras.layers.Activation("relu")
        self.gmp = tf.keras.layers.GlobalMaxPooling2D()

    def call(self, x, training=False):
        # residual convolution
        x = self.res1(x, training=training)
        x = self.res2(x, training=training)
        x = self.res3(x, training=training)
        x = self.res4(x, training=training)
        x = self.res5(x, training=training)
        x = self.res6(x, training=training)
        x = self.res7(x, training=training)

        # global max pooling
        # (16, 256)
        x = self.gmp(x)

        # fully connected
        x = self.fc_1(x, training=training)
        x = self.bn(x, training=training)
        x = self.relu(x, training=training)
        x = self.dropout(x, training=training)
        x = self.fc_2(x, training=training)
        x = self.activation(x, training=training)
        return x

############################################################
# modules for joint embeddings
#############################################################

class DenseLeakyReluLayer(tf.keras.layers.Layer):
    """A dense layer followed by a LeakyRelu layer"""

    def __init__(self, n, alpha=0.3):
        super(DenseLeakyReluLayer, self).__init__()
        self.dense = tf.keras.layers.Dense(n, activation=None)
        self.norm = tf.keras.layers.BatchNormalization()
        self.lrelu = tf.keras.layers.LeakyReLU(alpha=alpha)

    def call(self, input_tensor):
        x = self.dense(input_tensor)
        return self.lrelu(x)

class UnitNormLayer(tf.keras.layers.Layer):
    """Normalize vectors (euclidean norm) in batch to unit hypersphere."""

    def __init__(self):
        super(UnitNormLayer, self).__init__()

    def call(self, input_tensor):
        norm = tf.norm(input_tensor, axis=1)
        return input_tensor / tf.reshape(norm, [-1, 1])

class Wave_ResNet(tf.keras.layers.Layer):
    def __init__(self, input_channels, conv_channels=128, dataset="mtat"):
        super(Wave_ResNet, self).__init__()
        if dataset == "mtat":
            self.num_class = 50
        elif dataset == "dcase":
            self.num_class = 17
        elif dataset == "keyword":
            self.num_class = 35

        self.res1 = Conv3_2d(conv_channels, 2)
        self.res2 = Conv3_2d_resmp(conv_channels, 2)
        self.res3 = Conv3_2d_resmp(conv_channels, 2)
        self.res4 = Conv3_2d(conv_channels * 2, 2)
        self.res5 = Conv3_2d_resmp(conv_channels * 2, 2)
        self.res6 = Conv3_2d_resmp(conv_channels * 2, (2, 3))
        self.res7 = Conv3_2d(conv_channels * 2 * 2, (2, 3))
        # fully connected
        self.gmp = tf.keras.layers.GlobalMaxPooling2D()
        self.concat = tf.keras.layers.Concatenate()

    def call(self, x, training=False):
        # residual convolution
        x1 = self.res1(x, training=training)
        x2 = self.res2(x1, training=training)
        x3 = self.res3(x2, training=training)
        x4 = self.res4(x3, training=training)
        x5 = self.res5(x4, training=training)
        x6 = self.res6(x5, training=training)
        x7 = self.res7(x6, training=training)

        return self.concat([self.gmp(x5), self.gmp(x6), self.gmp(x7)])

class Conv3_2d(tf.keras.layers.Layer):
    def __init__(self, output_channels, pooling=2):
        super(Conv3_2d, self).__init__()
        self.conv = tf.keras.layers.Conv2D(
            output_channels, kernel_size=(3, 3), padding="same"
        )
        self.bn = tf.keras.layers.BatchNormalization()
        self.relu = tf.keras.layers.Activation("relu")
        self.mp = tf.keras.layers.MaxPooling2D((pooling))

    def call(self, x, training=False):
        out = self.mp(
            self.relu(self.bn(self.conv(x, training=training), training=training)),
            training=training,
        )
        return out

class Conv3_2d_resmp(tf.keras.layers.Layer):
    def __init__(self, output_channels, pooling=2):
        super(Conv3_2d_resmp, self).__init__()
        self.conv_1 = tf.keras.layers.Conv2D(
            output_channels, kernel_size=(3, 3), padding="same"
        )
        self.bn_1 = tf.keras.layers.BatchNormalization()
        self.conv_2 = tf.keras.layers.Conv2D(
            output_channels, kernel_size=(3, 3), padding="same"
        )
        self.bn_2 = tf.keras.layers.BatchNormalization()
        self.relu = tf.keras.layers.Activation("relu")
        self.mp = tf.keras.layers.MaxPooling2D((pooling))

    def call(self, x, training=False):
        out = self.bn_2(
            self.conv_2(
                self.relu(
                    self.bn_1(self.conv_1(x, training=training), training=training)
                ),
                training=training,
            ),
            training=training,
        )
        out = x + out
        out = self.mp(self.relu(out), training=training)
        return out

##########################################################
# Modules for resemul
#########################################################
def se_fn(x, amplifying_ratio):
    num_features = x.shape[-1]
    x = GlobalAvgPool1D()(x)
    x = Reshape((1, num_features))(x)
    x = Dense(
        num_features * amplifying_ratio,
        kernel_initializer="glorot_uniform",
    )(x)
    x = Activation("relu")(x)
    x = Dense(num_features, activation="sigmoid", kernel_initializer="glorot_uniform")(
        x
    )
    return x

def se_block(x, num_features, weight_decay, amplifying_ratio):
    x = basic_block(x, num_features, weight_decay, amplifying_ratio)
    x = Multiply()([x, se_fn(x, amplifying_ratio)])
    return x

def basic_block(x, num_features, weight_decay, _):
    x = Conv1D(
        num_features,
        kernel_size=3,
        padding="same",
        use_bias=True,
        kernel_regularizer=l2(weight_decay),
        kernel_initializer="he_uniform",
    )(x)
    x = BatchNormalization()(x)
    x = Activation("relu")(x)
    x = MaxPool1D(pool_size=3)(x)
    return x

def rese_block(x, num_features, weight_decay, amplifying_ratio):
    if num_features != x.shape[-1]:
        shortcut = Conv1D(
            num_features,
            kernel_size=1,
            padding="same",
            use_bias=True,
            kernel_regularizer=l2(weight_decay),
            kernel_initializer="glorot_uniform",
        )(x)
        shortcut = BatchNormalization()(shortcut)
    else:
        shortcut = x
    x = Conv1D(
        num_features,
        kernel_size=3,
        padding="same",
        use_bias=True,
        kernel_regularizer=l2(weight_decay),
        kernel_initializer="he_uniform",
    )(x)
    x = BatchNormalization()(x)
    x = Activation("relu")(x)
    x = Dropout(0.2)(x)
    x = Conv1D(
        num_features,
        kernel_size=3,
        padding="same",
        use_bias=True,
        kernel_regularizer=l2(weight_decay),
        kernel_initializer="he_uniform",
    )(x)
    x = BatchNormalization()(x)
    if amplifying_ratio > 0:
        x = Multiply()([x, se_fn(x, amplifying_ratio)])
    x = Add()([shortcut, x])
    x = Activation("relu")(x)
    x = MaxPool1D(pool_size=3)(x)
    return x

class MusicSinc1D(Layer):
    def __init__(
        self, N_filt=256, Filt_dim=2501, fs=16000, stride=1, padding="SAME", **kwargs
    ):
        self.N_filt = N_filt
        self.Filt_dim = Filt_dim
        self.fs = fs
        self.stride = stride
        self.padding = padding

        super(MusicSinc1D, self).__init__(**kwargs)

    def build(self, input_shape):
        # The filters are trainable parameters.
        self.filt_b1 = self.add_weight(
            name="filt_b1",
            shape=(self.N_filt, 1),
            initializer="uniform",
            trainable=True,
        )
        self.filt_band = self.add_weight(
            name="filt_band",
            shape=(self.N_filt, 1),
            initializer="uniform",
            trainable=True,
        )

        # Mel Initialization of the filterbanks
        low_freq_mel = 80
        high_freq_mel = 2595 * np.log10(1 + (self.fs / 2) / 700)  # Convert Hz to Mel
        mel_points = np.linspace(
            low_freq_mel, high_freq_mel, self.N_filt
        )  # Equally spaced in Mel scale
        f_cos = 700 * (10 ** (mel_points / 2595) - 1)  # Convert Mel to Hz
        b1 = np.roll(f_cos, 1)
        b2 = np.roll(f_cos, -1)
        b1[0] = 30
        b2[-1] = (self.fs / 2) - 100
        self.B1 = np.expand_dims(b1, axis=-1)
        self.B2 = np.expand_dims(b2, axis=-1)
        self.freq_scale = self.fs * 1.0

        t_right = tf.constant(
            tf.linspace(1.0, (self.Filt_dim - 1) / 2, int((self.Filt_dim - 1) / 2))
            / self.fs,
            tf.float32,
        )
        self.T_Right = tf.tile(tf.expand_dims(t_right, axis=0), (self.N_filt, 1))

        n = tf.linspace(0, self.Filt_dim - 1, self.Filt_dim)
        window = 0.54 - 0.46 * tf.cos(2 * math.pi * n / self.Filt_dim)
        window = tf.cast(window, tf.float32)
        self.Window = tf.tile(tf.expand_dims(window, axis=0), (self.N_filt, 1))

        self.set_weights(
            [self.B1 / self.freq_scale, (self.B2 - self.B1) / self.freq_scale]
        )

        super(MusicSinc1D, self).build(input_shape)  # Be sure to call this at the end

    def call(self, x, **kwargs):

        min_freq = 50.0
        min_band = 50.0

        filt_beg_freq = tf.abs(self.filt_b1) + min_freq / self.freq_scale
        filt_end_freq = filt_beg_freq + (
            tf.abs(self.filt_band) + min_band / self.freq_scale
        )

        low_pass1 = 2 * filt_beg_freq * self.sinc(filt_beg_freq * self.freq_scale)
        low_pass2 = 2 * filt_end_freq * self.sinc(filt_end_freq * self.freq_scale)
        band_pass = low_pass2 - low_pass1
        band_pass = band_pass / tf.reduce_max(band_pass, axis=1, keepdims=True)
        windowed_band_pass = band_pass * self.Window

        filters = tf.transpose(windowed_band_pass)
        filters = tf.reshape(filters, (self.Filt_dim, 1, self.N_filt))

        # Do the convolution.
        out = tf.nn.conv1d(x, filters=filters, stride=self.stride, padding=self.padding)

        return out

    def sinc(self, band):
        y_right = tf.sin(2 * band * self.T_Right) / (2 * band * self.T_Right)
        y_left = tf.reverse(y_right, [1])
        y = tf.concat([y_left, tf.ones((self.N_filt, 1)), y_right], axis=1)
        return y

    def compute_output_shape(self, input_shape):
        new_size = conv_utils.conv_output_length(
            input_shape[1],
            self.Filt_dim,
            padding=self.padding.lower(),
            stride=self.stride,
            dilation=1,
        )
        return (input_shape[0],) + (new_size,) + (self.N_filt,)


import time
import numpy as np
from src import Photons
from src import Renderer
from src.CDFLookup2D import CDFLookup2D
from src.CrossSectionLookup import CrossSectionLookup
from src.CDFLookup import CDFLookup
import matplotlib.pyplot as plt
from tqdm import tqdm
import pandas as pd

def calc_absorbed_dose(photon_number, photon_energy, cross_section_lookup, compton_look_up, rayleigh_look_up, xmin, xmax, ymin, ymax):
    df = Photons.initiate_pencil_beam(photon_energy, photon_number)
    depositions = Photons.calculate_depositions(df, cross_section_lookup, compton_look_up, rayleigh_look_up, xmin, xmax, ymin, ymax)
    return np.sum(depositions['energy'].values) / photon_number

if __name__ == '__main__':
    cross_section_lookup = CrossSectionLookup.from_csv('lookups/scattering crossections water.csv', has_keys = True)
    compton_look_up = CDFLookup2D.from_csv('lookups/compton angles.csv')
    rayleigh_look_up = CDFLookup.from_csv('lookups/rayleigh angles.csv')

    photon_number = 10 ** 7  # number of photons per run
    photon_energy = 0.1  # MeV
    loops = 10

    print(f"starting {loops} loops of {int(photon_number/loops)} photons each...")

    start = time.time()

    dep_doses = []
    all_depositions = []
    for i in tqdm(range(loops)):
        depositions = Photons.calculate_depositions(
            photon_energy, int(photon_number/loops), cross_section_lookup, compton_look_up, rayleigh_look_up,
            -2.5, 2.5, 0, 10, start_index = int(photon_number/loops)*i)
        if i == 0:
            all_depositions = depositions
        else:
            all_depositions = pd.concat([all_depositions, depositions])
        dep_doses.append(np.sum(depositions['energy']))

    time_taken = time.time() - start

    print(f"total deposition: {np.sum(dep_doses) / photon_number / photon_energy * 100:.3f}% of incoming energy")
    print(f"per run stab of deposition: {np.std(dep_doses) / photon_number / photon_energy * 100:.3f}% of incoming energy")

    print(f"sampling done in {int(time_taken / 60)}min {time_taken % 60 : .2f}s")
    print(f"\t {time_taken * 10 ** 6 / photon_number : .3f}s / million photons")

    Renderer.draw_photon_paths((-2.5, 0), (5, 10),
                               all_depositions, all_depositions['id'][:5],
                               "Path of First 5 Interacting Photons", scale_points = False)
    plt.draw()

    print("creating photon path image done")

    start = time.time()
    Renderer.draw_deposition_image((-2.5, 0), (5, 10), all_depositions, res = 100, title = 'interaction loop', log = True)
    time_taken = time.time() - start
    print(f"rendering done in {int(time_taken / 60)}min {time_taken % 60 : .2f}s")
    print(f"\t {time_taken * 10 ** 6 / len(all_depositions) : .3f}s / million interactions")

    plt.show()


import easyocr
import cv2
from matplotlib import pyplot as plt
import numpy as np

#Read an image/ video
IMAGE_PATH ='out_of_service.jpg'
reader = easyocr.Reader(['en'], gpu=False)
result =reader.readtext(IMAGE_PATH)
# print(result)
# print(result[0])

#handing multiple lines:
img =cv2.imread(IMAGE_PATH)
for detection in result:
    top_left = tuple([int(val) for val in detection[0][0]])
    bottom_right = tuple([int(val) for val in detection[0][2]])
    text = detection[1]
    font = cv2.FONT_HERSHEY_SIMPLEX
    img = cv2.rectangle(img, top_left, bottom_right, (0, 255, 0), 5)
    img = cv2.putText(img, text, top_left, font, 2, (255, 255, 255), 2,  cv2.LINE_AA)

plt.figure(figsize=(10, 10))
plt.imshow(img)
plt.show()
#Draw result:
# top_left = tuple(result[0][0][0])
# bottom_right = tuple(result[0][0][2])
# text = result[0][1]
# font = cv2.FONT_HERSHEY_SIMPLEX

# img = cv2.imread(IMAGE_PATH)
# img = cv2.rectangle(img, top_left, bottom_right, (0, 255, 0), 5)
# # img = cv2.rectangle(img, top_left, bottom_right, (0, 255, 255), 5)
# img = cv2.putText(img, text, top_left, font, .5, (255, 255, 255), 2,  cv2.LINE_AA)

# plt.imshow(img)
# plt.show()


# Originally created by Suriya Arulselvan in Dec 2015
# Developed by Zeyu Yang in Jul 2016
# Addtional developement is given by Kensuke Suzuki in 2020
# The method used in this file is the same as study by Kawajiri and Biegler
# Bibliographic information: Kawajiri Y, Biegler LT, Optimization Strategies for Simulated Moving Bed and PowerFeed Processes. AIChE Journal. 2006;52:1343-1350.
#                          : Zavala V, Biegler LT, Large-scale parameter estimation in low-density polyethylene tubular reactors. Industrial and Engineering Chemistry Research. 2006;45,23;7867-7881
#                          : Tie et al., Experimental evaluation of simulated moving bed reactor for transesterification reaction synthesis of glycol ether ester. Adsorption. 2019;25,4;795-807

# ==================================================================
# Pyomo script file
# ==================================================================

from pyomo.environ import *
from pyomo.dae import *
from pyomo.opt import SolverFactory
from SMB_Model_ParamEst import m
from Initdata_SMB_ParamEst import *
from GaussRadauQuadrature import lglnodes
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
import datetime
import sys
import os

dirname00 = f"Prameter_Estimation_Result_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}/"
os.makedirs(dirname00, exist_ok=True)

class Logger(object):
    def __init__(self):
        self.terminal = sys.stdout
        self.log = open(f"{dirname00}Output_ParamEst_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.txt", "a")
    def write(self, message):
        self.terminal.write(message)
        self.log.write(message)
    def flush(self):
        pass

sys.stdout = Logger()

sys.stderr = Logger()

# -------------------------------------------------------------------
# Display implementation details
# -------------------------------------------------------------------

print(f"\n----- Discritization Scheme for Axial Coordinate: {DScheme} -----")
print(f"\n----- Equilibrium Isotherm: {Isotype} -----")
print(f"\n----- LDF model based on {Phase} phase -----")

if Axial_D == True:
    print(f"\n----- Axial dispersion is implemented -----")
elif Axial_D == False:
	pass
else:
    sys.exit("ERROR: Confirm Axial_D definition")

if DV == True:
    print("\n----- Dead Volume is implemented -----")
elif DV == False:
    pass
else:
    sys.exit("ERROR: Confirm DeadVolume definition")

if Tikhonov == True:
    if EVM == True:
        print("\n----- Tikhonov regularization and EVM formulation are implemented -----")
    elif EVM == False:
        print("\n----- Tikhonov regularization is implemented -----")
    else:
        sys.exit("ERROR: Confirm EVM definition")

elif Tikhonov == False:
    print("\n----- Least square method is implemented -----")
else:
    sys.exit("ERROR: Confirm Tikhonov definition")

# -------------------------------------------------------------------
# Zone Difinition
# -------------------------------------------------------------------

Nfex = Nfex+1
Zone = int(Colum/NZone)

# -------------------------------------------------------------------
# Set Constraint List
# -------------------------------------------------------------------

m.U_constraints=ConstraintList()
m.HT_constraints=ConstraintList()

# -------------------------------------------------------------------
# Create Instance
# -------------------------------------------------------------------

instance = m.create_instance()

# -------------------------------------------------------------------
# Discretize time and space using collocation and finite difference, respectively
# -------------------------------------------------------------------
#Discretize model using Radau Collocation method
discretizer = TransformationFactory('dae.collocation')
#Discretize model using finite difference method
discretizer2 = TransformationFactory('dae.finite_difference')

#nfe=number of finite elements
#ncp=number of collocation points within each finite element, (scheme=OO, default is ‘LAGRANGE-RADAU’)
discretizer.apply_to(instance,nfe=Nfet, ncp=value(instance.NCP) , wrt=instance.t, scheme='LAGRANGE-RADAU')

discretizer2.apply_to(instance,nfe=Nfex-1, wrt=instance.x, scheme=DScheme)

if DV == True:
    discretizer2.apply_to(instance,nfe=Ncstr, wrt=instance.xRe, scheme='BACKWARD')

# -------------------------------------------------------------------
# Objective Function
# -------------------------------------------------------------------

def obj1_expr(m):
    return sum(m.e[d] for d in m.Data)
instance.obj1 = Objective(rule = obj1_expr, sense = minimize)

# Objective function 2 to estimate model parameters together with fluid velocities

def obj2_expr(m):
    LS_term = (1/len(m.Data))*sum((m.CbarE[d,i] - m.averageCE[d,i])**2 + (m.CbarR[d,i] - m.averageCR[d,i])**2 for d in m.Data for i in m.Comp)
    Tikhonov_term = 0
    EVM_term = 0
    if Tikhonov == True:
        Tikhonov_term += sum(((L2H[i-1] - m.H[i])/L2H[i-1])**2 + ((L2Kap[i-1] - m.Kap[i])/L2Kap[i-1])**2 for i in m.Comp)
        if Isotype == "Langmuir" or Isotype == "anti-Langmuir":
            Tikhonov_term += sum(((L2b[i-1] - m.b[i])/L2b[i-1])**2 for i in m.Comp)
        if Axial_D == True:
            Tikhonov_term += sum(((L2Dax[i-1] - m.Dax[i])/L2Dax[i-1])**2 for i in m.Comp)
        if DV == True:
            Tikhonov_term += ((L2LRe - m.LRe)/L2LRe)**2
        if EVM == True:
            EVM_term += (1/len(m.Data))*sum(((m.UD[d,0*Zone+1,m.t[2]] - UDinit[d-1])/UDinit[d-1])**2 + ((m.UE[d,1*Zone,m.t[2]] - UEinit[d-1])/UEinit[d-1])**2 +
                                            ((m.UF[d,2*Zone+1,m.t[2]] - UFinit[d-1])/UFinit[d-1])**2 + ((m.U[d,3*Zone+1,m.t[2]] - UReinit[d-1])/UReinit[d-1])**2 for d in m.Data)
    return LS_term + m.rho[1]*Tikhonov_term + m.rho[2]*EVM_term

instance.obj2 = Objective(rule = obj2_expr)

# -------------------------------------------------------------------
# Calculating weights of Gauss-Radau quadrature
# -------------------------------------------------------------------

w=lglnodes(value(instance.NCP))

def Ac_init(m,cp):
    return w[cp-1]/2.0
instance.Ac = Param(instance.CP,initialize=Ac_init,default=0)

# -------------------------------------------------------------------
# Defining calculation of Integrals with Gauss-Radau quadrature
# -------------------------------------------------------------------

def _intCR(m, d,i,j):
    return sum(m.UR[d,j,m.t[(k-1)*m.NCP+1+l]]*m.HT[d,m.t[(k-1)*m.NCP+1+l]]*(m.Ac[l]*m.C[d,i,j,m.t[1+(k-1)*m.NCP+l],value(m.L)]) for l in range(1,m.NCP+1) for k in range(1,Nfet+1)) == m.intCR[d,i,j]
instance.intCRrule = Constraint(instance.Data, instance.Comp, instance.Col,rule=_intCR)

def _intCE(m, d,i,j):
    return sum(m.UE[d,j,m.t[(k-1)*m.NCP+1+l]]*m.HT[d,m.t[(k-1)*m.NCP+1+l]]*(m.Ac[l]*m.C[d,i,j,m.t[1+(k-1)*m.NCP+l],value(m.L)]) for l in range(1,m.NCP+1) for k in range(1,Nfet+1)) == m.intCE[d,i,j]
instance.intCErule = Constraint(instance.Data, instance.Comp, instance.Col,rule=_intCE)

# -------------------------------------------------------------------
# Activate if CENTRAL scheme is selected
# -------------------------------------------------------------------

# Describing last axial derivate as a backward finite-difference method with 2nd-order
if DScheme == 'CENTRAL':
    def C_AxialDerivativeConstraintEnd_rule(m,d,i,j,k):
        return m.dCdx[d,i,j,k,value(m.L)]==(3*m.C[d,i,j,k,value(m.L)]-4*m.C[d,i,j,k,m.x[Nfex-1]]+m.C[d,i,j,k,m.x[Nfex-2]])/(2*m.L/(Nfex-1))
    instance.C_AxialDerivativeConstraintEnd=Constraint(instance.Data, instance.Comp, instance.Col, instance.t,  rule=C_AxialDerivativeConstraintEnd_rule)
    if Axial_D == True:
        def C_Axial2ndDerivativeConstraintEnd_rule(m,d,i,j,k):
            return m.dCdx2[d,i,j,k,value(m.L)]==(2*m.C[d,i,j,k,value(m.L)]-5*m.C[d,i,j,k,m.x[Nfex-1]]+4*m.C[d,i,j,k,m.x[Nfex-2]]-m.C[d,i,j,k,m.x[Nfex-3]])/((m.L/(Nfex-1))**3)
        instance.C_Axial2ndDerivativeConstraintEnd=Constraint(instance.Data, instance.Comp, instance.Col, instance.t,  rule=C_Axial2ndDerivativeConstraintEnd_rule)

# -------------------------------------------------------------------
# Fixing variables using Build Action
# -------------------------------------------------------------------

# ------------------------------------------------------
# Fixing model parameter at initial value
# ------------------------------------------------------

def Kap_Fix_rule(m):
  for i in sorted(m.Comp):
      m.Kap[i].fix(Kapinit[i-1])
instance.Kap_Fix = BuildAction(rule = Kap_Fix_rule)

def H_Fix_rule(m):
  for i in sorted(m.Comp):
      m.H[i].fix(Hinit[i-1])
instance.H_Fix = BuildAction(rule = H_Fix_rule)

if Isotype == "Langmuir" or Isotype == "anti-Langmuir":
    def b_Fix_rule(m):
        for i in sorted(m.Comp):
            m.b[i].fix(binit[i-1])
    instance.b_Fix = BuildAction(rule = b_Fix_rule)

if Axial_D == True:
    def Dax_Fix_rule(m):
        for i in sorted(m.Comp):
            m.Dax[i].fix(Daxinit[i-1])
    instance.Dax_Fix = BuildAction(rule = Dax_Fix_rule)

# ------------------------------------------------------
# Fixing manipulated velocities
# ------------------------------------------------------

# Feed is only supplied from inlet of first colmun in Zone 3
def UF_Fix_rule(m):
    for d in sorted(m.Data):
        for j in m.t:
            for i in m.Col:
                if i==(2*Zone+1):
                    m.UF[d,i,j].fix(UFinit[d-1])
                else:
                    m.UF[d,i,j].fix(0)
instance.UF_Fix = BuildAction(rule = UF_Fix_rule)

# Extract is only collected from outlet of last colmun in Zone 1
def UE_Fix_rule(m):
    for d in sorted(m.Data):
        for j in m.t:
            for i in m.Col:
                if i==(1*Zone):
                    m.UE[d,i,j].fix(UEinit[d-1])
                else:
                    m.UE[d,i,j].fix(0)
instance.UE_Fix = BuildAction(rule = UE_Fix_rule)

# Desorbent is only supplied from inlet of first colmun in Zone 1
def UD_Fix_rule(m):
    for d in sorted(m.Data):
        for j in m.t:
            for i in m.Col:
                if i==(0*Zone+1):
                    m.UD[d,i,j].fix(UDinit[d-1])
                else:
                    m.UD[d,i,j].fix(0)
instance.UD_Fix = BuildAction(rule = UD_Fix_rule)

# Raffinate is only collected from outlet of last colmun in Zone 3
def UR_Fix_rule(m):
    for d in sorted(m.Data):
        for j in m.t:
            for i in m.Col:
                if i!=(3*Zone):
                    m.UR[d,i,j].fix(0)
instance.UR_Fix = BuildAction(rule = UR_Fix_rule)

def U4Fix_rule(m):
    for d in sorted(m.Data):
        for x in range(3*Zone+1, 4*Zone+1):
            for j in m.t:
                m.U[d,x,j].fix(UReinit[d-1])
instance.U4Fix = BuildAction(rule = U4Fix_rule)

def StepTimeFix_rule(m):
    for d in sorted(m.Data):
        m.StepTime[d].fix(StepTimeInit[d-1])
instance.StepTimeFix = BuildAction(rule = StepTimeFix_rule)

# ------------------------------------------------------
# Fixing HT (Finite elements along time)
# ------------------------------------------------------

def HT_Fix_rule(m):
    for d in sorted(m.Data):
        for i in m.t:
            m.HT[d,i].fix(1/Nfet)
instance.HT_Fix = BuildAction(rule = HT_Fix_rule)

# -------------------------------------------------------------------
# Activate and Deactivate Constraints
# -------------------------------------------------------------------

for d in instance.Data:
    for i in instance.Comp:
        instance.averageCE[d,i].fix(0)
        instance.averageCR[d,i].fix(0)

# -------------------------------------------------------------------
# Selecting Objective
# -------------------------------------------------------------------

instance.obj1.activate()
instance.obj2.deactivate()

# -------------------------------------------------------------------
# Solver options
# -------------------------------------------------------------------

#### Create the ipopt_sens solver plugin using the ASL interface
solver = 'ipopt_sens'
solver_io = 'nl'
stream_solver = True    # True prints solver output to screen
keepfiles =     False    # True prints intermediate file names (.nl,.sol,...)
opt = SolverFactory(solver,solver_io=solver_io)
####

opt.options['mu_init'] = 1e-5
#opt.options['ma57_pivtol'] = 1e-8
opt.options['max_iter'] = 5000
# opt.options['linear_system_scaling'] = 'mc19'
#
# opt.options['linear_solver'] = 'ma97'
# opt.options['linear_solver'] = 'ma57'
opt.options['linear_solver'] = 'ma27'

# -------------------------------------------------------------------
# Solve successively
# -------------------------------------------------------------------

if DV == True:
    instance.LRe.fix(LReinit)

for i in range(ite_e):
    for d in instance.Data:
        instance.e[d].fix(1.0*10**-(8-ite_e+i+1))

    print(f"e = 1.0e{-(8-ite_e+i+1)}")

    instance.preprocess()
    results = opt.solve(instance, tee=True)
    instance.load(results)

    instance.e.free()

instance.preprocess()
results = opt.solve(instance, keepfiles=keepfiles, tee=stream_solver)
instance.load(results)

# -------------------------------------------------------------------
# Display Results
# -------------------------------------------------------------------

print("\n-------------------Initial Condition Solution----------------------\n")

print("--------------------------------------------------")
print("Initial parameters")
if DV == True:
    print(f"Recycle Length = {LReinit}")
for i in sorted(instance.Comp):
    print("Kap[%s] = %s" %(i,Kapinit[i-1]))
    print("H[%s] = %s" %(i,Hinit[i-1]))
    if Isotype == "Langmuir" or Isotype == "anti-Langmuir":
        print("b[%s] = %s" %(i,binit[i-1]))
    if Axial_D == True:
        print(f"Dax[{i}] = {Daxinit[i-1]}")
print("--------------------------------------------------")

print("\n")

for d in instance.Data:
    print("Data%s"%(d))
    for i in instance.Comp:
        print("recovery in Extract %s : \t %s" %(i, value(100*sum(instance.intCE[d,i,j] for j in instance.Col )/sum(instance.UF[d,j,k]*instance.CF[d,i]*instance.HT[d,k]/instance.NCP for j in instance.Col for k in instance.t if k != 0))))

    for i in instance.Comp:
        print("purity in Extract %s : \t %s" %(i, value(100*sum(instance.intCE[d,i,j] for j in instance.Col )/sum(instance.intCE[d,n,j] for n in instance.Comp for j in instance.Col ))))

    for i in instance.Comp:
        print("recovery in Raffinate %s : \t %s" %(i, value(100*sum(instance.intCR[d,i,j] for j in instance.Col )/sum(instance.UF[d,j,k]*instance.CF[d,i]*instance.HT[d,k]/instance.NCP for j in instance.Col for k in instance.t if k != 0))))

    for i in instance.Comp:
        print("purity in Raffinate %s : \t %s" %(i, value(100*sum(instance.intCR[d,i,j] for j in instance.Col )/sum(instance.intCR[d,n,j] for n in instance.Comp for j in instance.Col ))))

for d in instance.Data:
    print("Step Time(Data%s) %s" %(d,value(instance.StepTime[d])))

for d in instance.Data:
    print("Data%s" %(d))
    print("Comp A conc in Extract = %s" %(value((sum(instance.intCE[d,1,j] for j in instance.Col))/(sum(instance.UE[d,j,k]*instance.HT[d,k]/instance.NCP for j in instance.Col for k in instance.t if k != 0)))))
    print("Comp B conc in Extract = %s" %(value((sum(instance.intCE[d,2,j] for j in instance.Col))/(sum(instance.UE[d,j,k]*instance.HT[d,k]/instance.NCP for j in instance.Col for k in instance.t if k != 0)))))
    print("Comp A conc in Raffinate = %s" %(value((sum(instance.intCR[d,1,j] for j in instance.Col))/(sum(instance.UR[d,j,k]*instance.HT[d,k]/instance.NCP for j in instance.Col for k in instance.t if k != 0)))))
    print("Comp B conc in Raffinate = %s" %(value((sum(instance.intCR[d,2,j] for j in instance.Col))/(sum(instance.UR[d,j,k]*instance.HT[d,k]/instance.NCP for j in instance.Col for k in instance.t if k != 0)))))

print("\n")

for d in instance.Data:
    print(f"Data {d}: Objective function value (CSS constraint) = {value(instance.e[d])}")

print("\n")

if sum((value(instance.e[d])) for d in instance.Data) >= 3.0e-8:
    sys.exit("ERROR: Simulation not converged")
else:
    print("Simulation converged")

print("\n")

# -------------------------------------------------------------------
# Plot initialized graphs
# -------------------------------------------------------------------
if ini_plots == True:
    dirname0ini = dirname00 + f"InitialPlotfile_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}/"
    os.makedirs(dirname0ini, exist_ok=True)

    Col = []
    t = []
    X = []
    Comp = []
    CA = [[] for i in range(NData)]
    CB = [[] for i in range(NData)]
    S = np.zeros(NData)

    for i in sorted(instance.Col):
        Col.append(i)

    for j in range(NData):
        for i in sorted(instance.FET):
            S[j] = S[j] + value(instance.HT[j+1,instance.t[i*instance.NCP+1]])

    for i in sorted(instance.Comp):
        Comp.append(i)

    for k in range(NData):
        for i in sorted(instance.Col):
            for j in sorted(instance.x):
                CA[k].append(value(instance.C[k+1, 1, i, 1.0, j])/value(instance.CF[k+1,1]))
                CB[k].append(value(instance.C[k+1, 2, i, 1.0, j])/value(instance.CF[k+1,2]))

    for i in sorted(instance.Col):
        for j in sorted(instance.x):
            X.append(j + ColL*(i-1))

    filename = [[] for i in range(NData)]
    for i in range(NData):
        plt.figure(i+1)
        plt.plot(X, CA[i], 'o-', label='Comp A')
        plt.plot(X, CB[i], 'x-', label='Comp B')
        plt.legend()
        plt.xlim(0, ColL*Colum)
        plt.ylim(0, 1.2)
        plt.xlabel('x [m]')
        plt.ylabel('C/C(feed) [-]')
        plt.title(f"Data{i+1}_Concentration profile")
        filename[i] = dirname0ini + f"Data{i+1}_concentration_profile"
        plt.savefig(filename[i], dpi=500)
elif ini_plots == False:
    pass
else:
    sys.exit("ERROR: Confirm ini_plots definition")

# -------------------------------------------------------------------
# Setting fitting parameters free
# -------------------------------------------------------------------

for d in instance.Data:
    for i in instance.t:
        instance.UF[d,2*Zone+1,i].free()
        instance.UE[d,1*Zone,i].free()
        instance.UD[d,0*Zone+1,i].free()
        for x in range(3*Zone+1, 4*Zone+1):
            instance.U[d,x,i].free()

for i in instance.Comp:
    instance.H[i].free()
    instance.Kap[i].free()
    if Isotype == "Langmuir" or Isotype == "anti-Langmuir":
        instance.b[i].free()
    if Axial_D == True:
        instance.Dax[i].free()

for d in instance.Data:
    instance.e[d].fix(0)

if DV == True:
    instance.LRe.free()

for d in instance.Data:
    for i in instance.Comp:
        instance.averageCE[d,i].free()
        instance.averageCR[d,i].free()

# -------------------------------------------------------------------
# Selecting Objective
# -------------------------------------------------------------------

instance.obj1.deactivate()
instance.obj2.activate()

# -------------------------------------------------------------------
# Setting flow rate
# -------------------------------------------------------------------

for d in instance.Data:
    for i in range(1,Nfet):
        instance.U_constraints.add(instance.UF[d,2*Zone+1,instance.t[2]] == instance.UF[d,2*Zone+1,instance.t[i*instance.NCP+2]])
        instance.U_constraints.add(instance.UE[d,1*Zone,instance.t[2]] == instance.UE[d,1*Zone,instance.t[i*instance.NCP+2]])
        instance.U_constraints.add(instance.UD[d,0*Zone+1,instance.t[2]] == instance.UD[d,0*Zone+1,instance.t[i*instance.NCP+2]])
        for x in range(3*Zone+1, 4*Zone+1):
            instance.U_constraints.add(instance.U[d,x,instance.t[2]] == instance.U[d,x,instance.t[i*instance.NCP+2]])

# if HT_Const == True:
#     for d in instance.Data:
#         for i in range(1,Nfet):
#             instance.HT_constraints.add(instance.HT[d,instance.t[2]] == instance.HT[d,instance.t[i*instance.NCP+2]])
# elif HT_Const == False:
#     pass
# else:
#     print("ERROR: Confirm HT_Const definition")

for d in instance.Data:
    instance.StepTime[d].fix(StepTimeInit[d-1])
    for i in range(0,Nfet):
        for j in range(2,value(instance.NCP)+1):
            instance.U_constraints.add(instance.UF[d,2*Zone+1,instance.t[i*instance.NCP+j]] == instance.UF[d,2*Zone+1,instance.t[i*instance.NCP+j+1]])
            instance.U_constraints.add(instance.UE[d,1*Zone,instance.t[i*instance.NCP+j]] == instance.UE[d,1*Zone,instance.t[i*instance.NCP+j+1]])
            instance.U_constraints.add(instance.UD[d,0*Zone+1,instance.t[i*instance.NCP+j]] == instance.UD[d,0*Zone+1,instance.t[i*instance.NCP+j+1]])
            for x in range(3*Zone+1, 4*Zone+1):
                instance.U_constraints.add(instance.U[d,x,instance.t[i*instance.NCP+j]] == instance.U[d,x,instance.t[i*instance.NCP+j+1]])
    instance.U_constraints.add(instance.UF[d,2*Zone+1,instance.t[1]] == instance.UF[d,2*Zone+1,instance.t[2]])
    instance.U_constraints.add(instance.UE[d,1*Zone,instance.t[1]] == instance.UE[d,1*Zone,instance.t[2]])
    instance.U_constraints.add(instance.UD[d,0*Zone+1,instance.t[1]] == instance.UD[d,0*Zone+1,instance.t[2]])
    for x in range(3*Zone+1, 4*Zone+1):
        instance.U_constraints.add(instance.U[d,x,instance.t[1]] == instance.U[d,x,instance.t[2]])

if Tikhonov == True:
    if EVM == False:
        for d in instance.Data:
            instance.UF[d,2*Zone+1,instance.t[2]].fix(UFinit[d-1])
            instance.UE[d,1*Zone,instance.t[2]].fix(UEinit[d-1])
            instance.UD[d,0*Zone+1,instance.t[2]].fix(UDinit[d-1])
            for x in range(3*Zone+1, 4*Zone+1):
                instance.U[d,x,instance.t[2]].fix(UReinit[d-1])
elif Tikhonov == False:
    for d in instance.Data:
        instance.UF[d,2*Zone+1,instance.t[2]].fix(UFinit[d-1])
        instance.UE[d,1*Zone,instance.t[2]].fix(UEinit[d-1])
        instance.UD[d,0*Zone+1,instance.t[2]].fix(UDinit[d-1])
        for x in range(3*Zone+1, 4*Zone+1):
            instance.U[d,x,instance.t[2]].fix(UReinit[d-1])

# -------------------------------------------------------------------
# Caluculating average concentration in each product
# -------------------------------------------------------------------

# Adding constraints to change averageCE and CR from free variables to bound variables

def AveCE_rule(m, d,i):
    return m.averageCE[d,i]*sum(m.UE[d,1,l]*m.HT[d,l]/m.NCP for l in m.t if l != 0) == m.intCE[d,i,1]
instance.AveCE = Constraint(instance.Data,instance.Comp,rule=AveCE_rule)

def AveCR_rule(m, d,i):
    return m.averageCR[d,i]*sum(m.UR[d,3,l]*m.HT[d,l]/m.NCP for l in m.t if l != 0) == m.intCR[d,i,3]
instance.AveCR = Constraint(instance.Data,instance.Comp,rule=AveCR_rule)

# -------------------------------------------------------------------
# Activating constraints
# -------------------------------------------------------------------

# instance.HTSum.activate()
instance.U_constraints.activate()
# instance.HT_constraints.activate()

# -------------------------------------------------------------------
# Solver options
# -------------------------------------------------------------------

#### Create the ipopt_sens solver plugin using the ASL interface
solver = 'ipopt_sens'
solver_io = 'nl'
stream_solver = True    # True prints solver output to screen
keepfiles =     False    # True prints intermediate file names (.nl,.sol,...)
opt = SolverFactory(solver,solver_io=solver_io)
####

opt.options['mu_init'] = 1e-3
#opt.options['ma57_pivtol'] = 1e-8
opt.options['max_iter'] = 5000
# opt.options['linear_system_scaling'] = 'mc19'
#
# opt.options['linear_solver'] = 'ma97'
# opt.options['linear_solver'] = 'ma57'
opt.options['linear_solver'] = 'ma27'

# -------------------------------------------------------------------
# Solver options
# -------------------------------------------------------------------

###
instance.eta1 = Var()

nominal_eta1   = 5
perturbed_eta1 = 5

instance.consteta1 = Constraint(expr=instance.eta1 == nominal_eta1)
###

### declare suffixes
instance.sens_state_0 = Suffix(direction=Suffix.EXPORT)
instance.sens_state_1 = Suffix(direction=Suffix.EXPORT)
instance.sens_state_value_1 = Suffix(direction=Suffix.EXPORT)
instance.sens_sol_state_1  = Suffix(direction=Suffix.IMPORT)
instance.sens_init_constr  = Suffix(direction=Suffix.EXPORT)
instance.red_hessian = Suffix(direction=Suffix.EXPORT)
###

### set sIPOPT data
opt.options['run_sens'] = 'yes'
opt.options['compute_red_hessian'] = 'yes'
###

###
instance.sens_state_0[instance.eta1] = 1
instance.sens_state_1[instance.eta1] = 1
instance.sens_state_value_1[instance.eta1] = perturbed_eta1
instance.sens_init_constr[instance.consteta1] = 1
###

instance.red_hessian[instance.H[1]] = 1
instance.red_hessian[instance.H[2]] = 2
instance.red_hessian[instance.Kap[1]] = 3
instance.red_hessian[instance.Kap[2]] = 4

if Isotype == "Henry":
    if Axial_D == True:
        instance.red_hessian[instance.Dax[1]] = 5
        instance.red_hessian[instance.Dax[2]] = 6
        if DV == True:
            instance.red_hessian[instance.LRe] = 7
    elif Axial_D == False:
        if DV == True:
            instance.red_hessian[instance.LRe] = 5

elif Isotype == "Langmuir" or Isotype == "anti-Langmuir":
    instance.red_hessian[instance.b[1]] = 5
    instance.red_hessian[instance.b[2]] = 6
    if Axial_D == True:
        instance.red_hessian[instance.Dax[1]] = 7
        instance.red_hessian[instance.Dax[2]] = 8
        if DV == True:
            instance.red_hessian[instance.LRe] = 9
    elif Axial_D == False:
        if DV == True:
            instance.red_hessian[instance.LRe] = 7

### Send the model to ipopt_sens and collect the solution
instance.preprocess()
results = opt.solve(instance, keepfiles=keepfiles, tee=stream_solver)
instance.load(results)
###

# -------------------------------------------------------------------
# Display Results
# -------------------------------------------------------------------

print("\n-------------------Estimation Solution----------------------\n")

for d in instance.Data:
    print("Data%s"%(d))
    for i in instance.Comp:
        print("recovery in Extract %s : \t %s" %(i, value(100*sum(instance.intCE[d,i,j] for j in instance.Col )/sum(instance.UF[d,j,k]*instance.CF[d,i]*instance.HT[d,k]/instance.NCP for j in instance.Col for k in instance.t if k != 0))))

    for i in instance.Comp:
        print("purity in Extract %s : \t %s" %(i, value(100*sum(instance.intCE[d,i,j] for j in instance.Col )/sum(instance.intCE[d,n,j] for n in instance.Comp for j in instance.Col ))))

    for i in instance.Comp:
        print("recovery in Raffinate %s : \t %s" %(i, value(100*sum(instance.intCR[d,i,j] for j in instance.Col )/sum(instance.UF[d,j,k]*instance.CF[d,i]*instance.HT[d,k]/instance.NCP for j in instance.Col for k in instance.t if k != 0))))

    for i in instance.Comp:
        print("purity in Raffinate %s : \t %s" %(i, value(100*sum(instance.intCR[d,i,j] for j in instance.Col )/sum(instance.intCR[d,n,j] for n in instance.Comp for j in instance.Col ))))

for d in instance.Data:
    print("Step Time(Data%s) %s" %(d,value(instance.StepTime[d])))

print("\n")

for d in instance.Data:
    print("\nData%s" %(d))
    print("Throughput: %s [m/hr]" %(value(3600*sum(instance.UF[d,j,k]*instance.HT[d,k]/instance.NCP for k in instance.t for j in instance.Col if k != 0))))
    print("Desorbent: %s [m/hr]" %(value(3600*sum(instance.UD[d,j,k]*instance.HT[d,k]/instance.NCP for k in instance.t for j in instance.Col if k != 0))))
    print("D/F ratio: %s" %(value((sum(instance.UD[d,j,k]*instance.HT[d,k]/instance.NCP for k in instance.t for j in instance.Col if k != 0)/(sum(instance.UF[d,j,k]*instance.HT[d,k]/instance.NCP for k in instance.t for j in instance.Col if k != 0))))))
    print("\nComp A conc in extract = %s" %(value((sum(instance.intCE[d,1,j] for j in instance.Col))/(sum(instance.UE[d,j,k]*instance.HT[d,k]/instance.NCP for j in instance.Col for k in instance.t if k != 0)))))
    print("Comp B conc in extract = %s" %(value((sum(instance.intCE[d,2,j] for j in instance.Col))/(sum(instance.UE[d,j,k]*instance.HT[d,k]/instance.NCP for j in instance.Col for k in instance.t if k != 0)))))
    print("Comp A conc in raffinate = %s" %(value((sum(instance.intCR[d,1,j] for j in instance.Col))/(sum(instance.UR[d,j,k]*instance.HT[d,k]/instance.NCP for j in instance.Col for k in instance.t if k != 0)))))
    print("Comp B conc in raffinate = %s" %(value((sum(instance.intCR[d,2,j] for j in instance.Col))/(sum(instance.UR[d,j,k]*instance.HT[d,k]/instance.NCP for j in instance.Col for k in instance.t if k != 0)))))
    print("\nUF = %s " %(value(instance.UF[d,2*Zone+1,instance.t[2]])))
    print("UE = %s " %(value(instance.UE[d,1*Zone,instance.t[2]])))
    print("UD = %s " %(value(instance.UD[d,0*Zone+1,instance.t[2]])))
    print("URe = %s " %(value(instance.U[d,3*Zone+1,instance.t[2]])))
    print("Fitting term = %s " %(value((instance.CbarE[d,1] - instance.averageCE[d,1])**2 + (instance.CbarR[d,1] - instance.averageCR[d,1])**2 + (instance.CbarE[d,2] - instance.averageCE[d,2])**2 + (instance.CbarR[d,2] - instance.averageCR[d,2])**2)))

print("\n")

if EVM == True:
    print("FlowRateError [%] = (UF,UE,UD,URe)")
    for d in instance.Data:
        print("Day%s = (%s,%s,%s,%s)" %(d,value((instance.UF[d,2*Zone+1,instance.t[2]]-UFinit[d-1])*100/UFinit[d-1]),value((instance.UE[d,1*Zone,instance.t[2]]-UEinit[d-1])*100/UEinit[d-1]),value((instance.UD[d,0*Zone+1,instance.t[2]]-UDinit[d-1])*100/UDinit[d-1]),value((instance.U[d,3*Zone+1,instance.t[2]]-UReinit[d-1])*100/UReinit[d-1])))

print("\n")

print("--------------------------------------------------")
if DV == True:
    print("Recycle Length = %s " %(value(instance.LRe)))
for i in sorted(instance.Comp):
    print("Kap[%s] = %s" %(i,value(instance.Kap[i])))
    print("H[%s] = %s" %(i,value(instance.H[i])))
    if Isotype == "Langmuir" or Isotype == "anti-Langmuir":
        print("b[%s] = %s" %(i,value(instance.b[i])))
    if Axial_D == True:
        print(f"Dax[{i}] = {value(instance.Dax[i])}")
print("--------------------------------------------------")
if Tikhonov == True:
    print("--------------------------------------------------")
    if EVM == True:
        print(f"regularization parameters = {instance.rho[1],instance.rho[2]}")
    elif EVM == False:
        print(f"regularization parameters = {instance.rho[1]}")
    print("--------------------------------------------------")
    if DV == True:
        print(f"Recycle Length Error = {value(instance.LRe)-L2LRe}")
    for i in sorted(instance.Comp):
        print(f"Kap[{i}] Error = {value(instance.Kap[i])-L2Kap[i-1]}")
        print(f"H[{i}] Error = {value(instance.H[i])-L2H[i-1]}")
        if Isotype == "Langmuir" or Isotype == "anti-Langmuir":
            print(f"b[{i}] Error = {value(instance.b[i])-L2b[i-1]}")
        if Axial_D == True:
            print(f"Dax[{i}] Error = {value(instance.Dax[i])-L2Dax[i-1]}")
print("--------------------------------------------------")
if Tikhonov == True:
    if DV == True:
        print(f"Recycle Length Error [%] = {(value(instance.LRe)-L2LRe)*100/(L2LRe)}")
    for i in sorted(instance.Comp):
        print(f"Kap[{i}] Error [%] = {(value(instance.Kap[i])-L2Kap[i-1])*100/L2Kap[i-1]}")
        print(f"H[{i}] Error [%] = {(value(instance.H[i])-L2H[i-1])*100/L2H[i-1]}")
        if Isotype == "Langmuir" or Isotype == "anti-Langmuir":
            print(f"b[{i}] Error [%] = {(value(instance.b[i])-L2b[i-1])*100/L2b[i-1]}")
        if Axial_D == True:
            print(f"Dax[{i}] Error = {(value(instance.Dax[i])-L2Dax[i-1])*100/L2Dax[i-1]}")
print("--------------------------------------------------")
if Tikhonov == True:
    print("L2 parameters in Tikhonov regularization term")
    if DV == True:
        print(f"Recycle Length = {L2LRe}")
    for i in sorted(instance.Comp):
        print("Kap[%s] = %s" %(i,L2Kap[i-1]))
        print("H[%s] = %s" %(i,L2H[i-1]))
        if Isotype == "Langmuir" or Isotype == "anti-Langmuir":
            print("b[%s] = %s" %(i,L2b[i-1]))
        if Axial_D == True:
            print(f"Dax[{i}] = {L2Dax[i-1]}")
print("--------------------------------------------------")

print("\n")

FittingTerm = np.zeros(5)
for d in instance.Data:
    FittingTerm[0] += value((instance.CbarE[d,1] - instance.averageCE[d,1])**2 + (instance.CbarR[d,1] - instance.averageCR[d,1])**2 + (instance.CbarE[d,2] - instance.averageCE[d,2])**2 + (instance.CbarR[d,2] - instance.averageCR[d,2])**2)
print("--------------------------------------------------")
print(f"Fitting term = {FittingTerm[0]}")
print(f"Fitting term divided by sample size = {(1/len(instance.Data))*(FittingTerm[0])}")
for d in instance.Data:
    FittingTerm[1] += value((instance.CbarE[d,1] - instance.averageCE[d,1])**2)
    FittingTerm[2] += value((instance.CbarR[d,1] - instance.averageCR[d,1])**2)
    FittingTerm[3] += value((instance.CbarE[d,2] - instance.averageCE[d,2])**2 )
    FittingTerm[4] += value((instance.CbarR[d,2] - instance.averageCR[d,2])**2)

print("--------------------------------------------------")
print(f"Fitting term of A in Extract = {FittingTerm[1]}")
print(f"Fitting term of A in Raffinate = {FittingTerm[2]}")
print(f"Fitting term of B in Extract = {FittingTerm[3]}")
print(f"Fitting term of B in Raffinate = {FittingTerm[4]}")
print("--------------------------------------------------")
print(f"Fitting term of A in Extract divided by sample size = {(1/len(instance.Data))*FittingTerm[1]}")
print(f"Fitting term of A in Raffinate divided by sample size = {(1/len(instance.Data))*FittingTerm[2]}")
print(f"Fitting term of B in Extract divided by sample size = {(1/len(instance.Data))*FittingTerm[3]}")
print(f"Fitting term of B in Raffinate divided by sample size = {(1/len(instance.Data))*FittingTerm[4]}")
print("--------------------------------------------------")

print("\nEstimation Completed")

# -------------------------------------------------------------------
# Plot graphs
# -------------------------------------------------------------------

dirname01 = dirname00 + f"Plotfile_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}/"
os.makedirs(dirname01, exist_ok=True)

Col = []
t = []
X = []
Comp = []
CA = [[] for i in range(NData)]
CB = [[] for i in range(NData)]
S = np.zeros(NData)

for i in sorted(instance.Col):
	Col.append(i)

for j in range(NData):
    for i in sorted(instance.FET):
        S[j] = S[j] + value(instance.HT[j+1,instance.t[i*instance.NCP+1]])

for i in sorted(instance.Comp):
	Comp.append(i)

for k in range(NData):
    for i in sorted(instance.Col):
        for j in sorted(instance.x):
            CA[k].append(value(instance.C[k+1, 1, i, 1.0, j])/value(instance.CF[k+1,1]))
            CB[k].append(value(instance.C[k+1, 2, i, 1.0, j])/value(instance.CF[k+1,2]))

for i in sorted(instance.Col):
    for j in sorted(instance.x):
        X.append(j + ColL*(i-1))

filename = [[] for i in range(NData)]
for i in range(NData):
    if ini_plots == True:
        plt.figure(i+1+NData)
    elif ini_plots == False:
        plt.figure(i+1)
    else:
        print("ERROR: Confirm ini_plots definition")
    plt.plot(X, CA[i], 'o-', label='Comp A')
    plt.plot(X, CB[i], 'x-', label='Comp B')
    plt.legend()
    plt.xlim(0, ColL*Colum)
    plt.ylim(0, 1.2)
    plt.xlabel('x [m]')
    plt.ylabel('C/C(feed) [-]')
    plt.title(f"Data{i+1}_Concentration profile")
    filename[i] = dirname01 + f"Data{i+1}_concentration_profile"
    plt.savefig(filename[i], dpi=500)

dirname02 = dirname00 + f"Animationfile_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}/"
os.makedirs(dirname02, exist_ok=True)

for l in range(NData):
    C1all=[]
    C2all=[]

    for k in sorted(instance.t):
        C1 = []
        C2 = []
        for i in sorted(instance.Col):
            for j in sorted(instance.x):
                C1.append((value(instance.C[l+1,1, i, k ,j]))/value(instance.CF[l+1,1]))
                C2.append((value(instance.C[l+1,2, i, k ,j]))/value(instance.CF[l+1,2]))
        C1all.append(C1)
        C2all.append(C2)

    C1all = np.array(C1all)
    C2all = np.array(C2all)

    C1_ani = C1all
    C2_ani = C2all

    C1all = np.hstack([C1all[:,3*Nfex:],C1all[:,:3*Nfex]])
    C2all = np.hstack([C2all[:,3*Nfex:],C2all[:,:3*Nfex]])
    C1_ani = np.vstack([C1_ani,C1all])
    C2_ani = np.vstack([C2_ani,C2all])

    C1all = np.hstack([C1all[:,3*Nfex:],C1all[:,:3*Nfex]])
    C2all = np.hstack([C2all[:,3*Nfex:],C2all[:,:3*Nfex]])
    C1_ani = np.vstack([C1_ani,C1all])
    C2_ani = np.vstack([C2_ani,C2all])

    C1all = np.hstack([C1all[:,3*Nfex:],C1all[:,:3*Nfex]])
    C2all = np.hstack([C2all[:,3*Nfex:],C2all[:,:3*Nfex]])
    C1_ani = np.vstack([C1_ani,C1all])
    C2_ani = np.vstack([C2_ani,C2all])

    fig = plt.figure(figsize = (8, 6))

    def update(i,u1,u2,x,fig_title):
        plt.cla()
        plt.ylim(0, 1.2)
        plt.xlim(0, ColL*Colum)
        plt.plot(x,u1[i,:], 'o-', label='Comp A')
        plt.plot(x,u2[i,:], 'x-', label='Comp B')
        plt.xlabel('x [m]')
        plt.ylabel('C/C(feed) [-]')
        plt.title(f"Data{l+1} SMB Concentration profile")
        plt.legend()

    nframe = Nfet*value(instance.NCP)+1
    nframe = nframe*4
    ani = animation.FuncAnimation(fig,update,fargs = (C1_ani,C2_ani,X,'Wave motion'),interval=100,frames=nframe)
    filename = dirname02 + f"Data{l+1}_Concentration_Animation.gif"
    ani.save(filename, writer="pillow", dpi=500)


from pprint import pprint
import random

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

from data import get_train_valid_loader, plot_images
from trainer import train
from model import ModelFromDecoding
from mutation import MUTATION_OPERATIONS
"""
Main module which represents the GA training
"""

class GA():
    def __init__(self,
                 expert_mode: bool = True,
                 initial_epochs: int = 3,
                 final_epochs: int = 10,
                 batch_size: int = 64,
                 n_individuals: int = 8,
                 cnn_depth: int = 10,
                 stopping_criteria: int = 100):
        """
        Generative Algorithm class which contains the lifecycle of generative algorithms.

        Args:
            expert_mode (bool, optional): Sample values based from human designed networks. Defaults to True.
            initial_epochs (int, optional): Initial epochs to train individual. Defaults to 3.
            final_epochs (int, optional): Final epochs that an individual is being trained. Defaults to 10.
            batch_size (int, optional): Batch size. Defaults to 64.
            n_individuals (int, optional): Number of individuals generated at initialization. Defaults to 8.
            cnn_depth (int, optional): Initial depth of architecture. Defaults to 10.
            stopping_criteria (int, optional): Number of generations where the algorithm should stop. Defaults to 100.
        """

        self.expert_mode = expert_mode
        self.batch_size = batch_size
        self.initial_epochs = initial_epochs
        self.final_epochs = final_epochs
        self.n_individuals = n_individuals
        self.cnn_depth = cnn_depth
        self.generations = 0
        self.stopping = stopping_criteria
        self.individuals = []
        self.offsprings = []
        self._generate_new_population(
        )  # initializes first pool at initialization
        self.fitness = {s: None for s in self.individuals}
        self.history = {}
        self.device = torch.device(
            'cuda') if torch.cuda.is_available() else torch.device('cpu')

    def _generate_new_population(self):
        """
        Generates initial population of string sequences representing a NN architecture
        """
        if self.expert_mode:
            choices = [
                64, 128, 256, 512
            ]  # known values that have worked well in current networks
        for _ in range(self.n_individuals):
            enc = ''
            for _ in range(self.cnn_depth):
                spec = random.uniform(0., 1.)
                if spec < 0.5:
                    conv_1 = str(random.randint(1, 512))
                    conv_2 = str(random.randint(1, 512))
                    if self.expert_mode:
                        conv_1 = str(random.choice(choices))
                        conv_2 = str(random.choice(choices))
                    enc += f'{conv_1}-{conv_2}-'
                else:
                    pool = str(round(random.uniform(0., 1.), 1))
                    enc += f'{pool}-'
            self.individuals.append(enc[:-1])

    def calc_fitness(self):
        """
        Calculates fitness (training set accuracy) for models that have not been trained
        """
        if torch.cuda.is_available():
            trainset, _ = get_train_valid_loader('./data/',
                                                 self.batch_size,
                                                 True,
                                                 42,
                                                 num_workers=1,
                                                 pin_memory=True)
        else:
            trainset, _ = get_train_valid_loader('./data/', self.batch_size,
                                                 True, 42)

        criterion = nn.CrossEntropyLoss()
        for i in self.individuals:
            if i not in self.fitness.keys():
                self.fitness[i] = None
            if self.fitness[i] is not None: continue
            model = ModelFromDecoding(i, self.device).to(self.device)
            epochs = self.initial_epochs if self.generations < (
                self.stopping // 2
            ) else self.final_epochs  # let model evolve faster in the start and train longer in the end
            self.fitness[i] = train(model,
                                    criterion,
                                    trainset,
                                    epochs=epochs,
                                    device=self.device)
        self.history[self.generations] = self.fitness

    def generate_offsprings(self, p_crossover: float, p_mutation: float,
                            mutation_operations: list):
        """
        Generates offsprings from sampled parents in current population.
        Offsprings have a certain chance to mutate.

        Args:
            p_crossover (float): Crossover probability
            p_mutation (float): Mutation probability
            mutation_operations (list): List of mutation operations which are defined in `mutation.py`
        """
        offsprings = []
        while len(offsprings) < len(self.individuals):
            p1, p2 = random.choices(self.individuals, k=2)
            if self.fitness[p1] < self.fitness[p2]:
                p1, p2 = p2, p1
            while p1 == p2:
                p1, p2 = random.choices(self.individuals, k=2)
            r = random.uniform(0., 1.)
            if r < p_crossover:
                p1_part = p1.split('-')
                p2_part = p2.split('-')
                crossover_point = random.randint(1, len(p1_part) - 1)
                o1 = '-'.join(p1_part[:crossover_point] +
                              p2_part[crossover_point:])
                o2 = '-'.join(p2_part[:crossover_point] +
                              p1_part[crossover_point:])
                offsprings.append(o1)
                offsprings.append(o2)
        for offspring in self.offsprings:
            r = random.uniform(0., 1.)
            if r < p_mutation:
                m = random.randint(0, len(mutation_operations) - 1)
                pi = random.randint(0, len(offspring.split('-')) - 1)
                offspring[pi] = mutation_operations[m](offspring[pi])
        self.offsprings.extend(offsprings)

    def selection(self,
                  add_n_to_population: int = 5,
                  remove_n_weakest: int = 4):
        """
        Selects best and drops worst performing individuals.

        Args:
            add_n_to_population (int, optional): Number of offsprings being added to the next generation. Defaults to 5.
            remove_n_weakest (int, optional): Number of individuals being dropped. Defaults to 4.
        """
        def remove_individual(least_fittest: str):
            """
            Helper function to remove individuals

            Args:
                least_fittest (str): Representation of individual
            """
            if least_fittest in self.individuals:
                self.individuals.remove(least_fittest)
                del self.fitness[least_fittest]
            else:
                return

        next_generation = []

        # sort individuals based on their fitness
        fittest = dict(
            sorted(self.fitness.items(), key=lambda i: i[1], reverse=True))
        rank = list(fittest.keys())

        for w in range(1, remove_n_weakest):
            rmv = rank[-w]
            remove_individual(rmv)

        # sample from pool of offsprings
        for _ in range(add_n_to_population):
            a, b = random.choices(self.offsprings, k=2)
            while a == b:
                a, b = random.choices(self.offsprings, k=2)
            next_generation.append(a)
            next_generation.append(b)

        individuals_2be = rank[:len(
            rank) // 2]  # select the upper best performing for next generation
        next_generation.extend(individuals_2be)

        self.individuals = next_generation
        self.offsprings.clear()

    def run(self):
        """
        Training loop.
        """
        while self.generations < self.stopping:
            print(f'Generation: {self.generations+1}')
            self.calc_fitness()
            print('Current Fitness:')
            pprint(self.fitness)
            self.generate_offsprings(random.uniform(0., 1.),
                                     random.uniform(0., 1.),
                                     MUTATION_OPERATIONS)
            self.selection()
            self.generations += 1
            # print('Next gen: ')
            # pprint(self.individuals)

if __name__ == '__main__':
    p = GA()
    p.run()


import colorama
from colorama import Fore
from components import setting, validation,  db
from PIL import Image
from emoji import emojize
# Chose Chaecter

def newQuiz():
    q = db.mQuestions.items()
    #Rertiving Data #
    currentQuestion = 0
    setting.userAnswers.clear()
    setting.userResult.clear()
    for value in q:
        print(Fore.LIGHTCYAN_EX + "\n\t" +
              str(value[0]) + ": " + str(value[1]))
        answer = validation.validation("inputAnswer")
        if (answer.lower() == "y"):
            for k in db.mVar[currentQuestion]:
                if (k == "b"):
                    db.mB += 1
                elif (k == "s"):
                    db.mS += 1
                elif (k == "c"):
                    db.mC += 1
                else:
                    db.mA += 1
        else:
            rest = set(['b', 's', 'c', 'a']) - set(db.mVar[currentQuestion])
            for k in rest:
                if (k == "b"):
                    db.mB += 1
                elif (k == "s"):
                    db.mS += 1
                elif (k == "c"):
                    db.mC += 1
                else:
                    db.mA += 1
        currentQuestion += 1

    # Display the result #
    print(Fore.GREEN + "\n\tThank You: " + emojize(':thumbs_up:'))

    imgPath = ""
    result = max(db.mA, db.mB, db.mC, db.mS)
    if(result == db.mA):
        im = Image.open("aquaman.png")
    elif(result == db.mB):
        im = Image.open("batman.jpg")
    elif(result == db.mC):
        im = Image.open("cyborg.jpg")
    else:
        im = Image.open("superman.jpg")
    im.show()
    # Play Again #
    pAgain = validation.validation("playAgain")

while (setting.playAgain == 1):
    newQuiz()


import time
from datetime import timedelta
from unittest.mock import Mock

from oozer.waiter import TaskWaiter

def test_waiter_should_terminate_when_processed_more_than_90percent():
    mock_tracker = Mock()
    mock_tracker.get_pulse.return_value = Mock(Total=95)
    with TaskWaiter('sweep-id', mock_tracker, 100, 0) as waiter:
        assert waiter.should_terminate()

def test_waiter_should_terminate_when_current_time_more_than_stop_time():
    mock_tracker = Mock()
    mock_tracker.get_pulse.return_value = Mock(Total=0)
    stop_time = time.time()
    with TaskWaiter('sweep-id', mock_tracker, 100, stop_time) as waiter:
        assert waiter.should_terminate()

def test_waiter_should_not_terminate():
    mock_tracker = Mock()
    mock_tracker.get_pulse.return_value = Mock(Total=0)
    stop_time = time.time() + timedelta(hours=1).total_seconds()
    with TaskWaiter('sweep-id', mock_tracker, 100, stop_time) as waiter:
        assert not waiter.should_terminate()


from ophyd.device import Component

from .signal import FakeSignal
from .sim import SimDevice

class Undulator(SimDevice):
    """
    Simulated undulator ophyd object.
    """

    sim_xp = Component(FakeSignal, value=0)
    sim_yp = Component(FakeSignal, value=0)

    def __init__(
        self,
        prefix,
        *,
        x=0,
        xp=0,
        y=0,
        yp=0,
        z=0,
        velo_x=0,
        velo_xp=0,
        velo_y=0,
        velo_yp=0,
        velo_z=0,
        noise_x=False,
        noise_xp=False,
        noise_y=False,
        noise_yp=False,
        noise_z=False,
        settle_time_x=0,
        settle_time_xp=0,
        settle_time_y=0,
        settle_time_yp=0,
        settle_time_z=0,
        noise_func=None,
        noise_type="uni",
        noise_args=(),
        noise_kwargs={},
        **kwargs
    ):
        super().__init__(prefix, **kwargs)
        self.log_pref = "{0} (Undulator) - ".format(self.name)
        # Simulation Attributes
        # Initial Values
        self.sim_x.put(x)
        self.sim_xp.put(xp)
        self.sim_y.put(y)
        self.sim_yp.put(yp)
        self.sim_z.put(z)
        # Velocity for every move
        self.sim_x.velocity = velo_x
        self.sim_xp.velocity = velo_xp
        self.sim_y.velocity = velo_y
        self.sim_yp.velocity = velo_yp
        self.sim_z.velocity = velo_z
        # Fake noise to readback and moves
        self.sim_x.noise = noise_x
        self.sim_xp.noise = noise_xp
        self.sim_y.noise = noise_y
        self.sim_yp.noise = noise_yp
        self.sim_z.noise = noise_z
        # Settle time for every move
        self.sim_x.put_sleep = settle_time_x
        self.sim_xp.put_sleep = settle_time_xp
        self.sim_y.put_sleep = settle_time_y
        self.sim_yp.put_sleep = settle_time_yp
        self.sim_z.put_sleep = settle_time_z
        # Noise type to use
        self.sim_x.noise_type = noise_type
        self.sim_xp.noise_type = noise_type
        self.sim_y.noise_type = noise_type
        self.sim_yp.noise_type = noise_type
        self.sim_z.noise_type = noise_type
        # Noise args for the noise function
        self.sim_x.noise_args = noise_args
        self.sim_xp.noise_args = noise_args
        self.sim_y.noise_args = noise_args
        self.sim_yp.noise_args = noise_args
        self.sim_z.noise_args = noise_args
        # Noise kwargs for the noise function
        self.sim_x.noise_kwargs = noise_kwargs
        self.sim_xp.noise_kwargs = noise_kwargs
        self.sim_y.noise_kwargs = noise_kwargs
        self.sim_yp.noise_kwargs = noise_kwargs
        self.sim_z.noise_kwargs = noise_kwargs


import requests
from bs4 import BeautifulSoup
import time
from datetime import date
from datetime import datetime
import datetime
import csv

######################
# Función de captura #
######################

def captura():

    page_olesa = requests.get("https://www.meteoclimatic.net/perfil/ESCAT0800000008640A")
    soup_olesa = BeautifulSoup(page_olesa.content)

    page_vacarisses = requests.get("https://www.meteoclimatic.net/perfil/ESCAT0800000008233A")
    soup_vacarisses = BeautifulSoup(page_vacarisses.content)

    #Buscamos los datos de interés que en la página se encuentran en el 'td' de clase "dadesactuals".
    #Seleccionamos todos los datos menos el último, ya que los días de sequía no nos interesan.
    dades_olesa = soup_olesa.find_all('td', class_="dadesactuals")[0:5]
    dades_vacarisses = soup_vacarisses.find_all('td', class_="dadesactuals")[0:6]

    #Generamos los vectores para almacenar los datos.
    meteo_dat_olesa = []
    meteo_dat_vacarisses = []

    #Almacenamos los strings en los vectores de datos.
    for elem in dades_olesa:
        meteo_dat_olesa.append(elem.get_text())
    for elem in dades_vacarisses:
        meteo_dat_vacarisses.append(elem.get_text())

    #Añadimos dos posiciones más a la lista ya que la dirección del viendo se descompone en dos variables.
    meteo_dat_olesa.insert(2,None)
    meteo_dat_vacarisses.insert(2,None)

    #Modificamos ahora cada uno de los strings para convertirlos en datos numéricos.
    meteo_dat_olesa[0] = float(meteo_dat_olesa[0][:4])
    meteo_dat_vacarisses[0] = float(meteo_dat_vacarisses[0][:4])
    meteo_dat_olesa[1] = int(meteo_dat_olesa[1][:2])
    meteo_dat_vacarisses[1] = int(meteo_dat_vacarisses[1][:2])

    #En la dirección del viento, el el segundo char aparece como \xa0 (espacio vacío)#
    #por ejemplo en N (N\xa0), este espacio vacío se corresponde con el caracter chr(160)#
    if meteo_dat_olesa[3][1:2] == chr(160):
        meteo_dat_olesa[2] = int(meteo_dat_olesa[3][3:4])
    elif meteo_dat_olesa[3][2:3] == chr(160):
        meteo_dat_olesa[2] = int(meteo_dat_olesa[3][4:5])
    else:
        meteo_dat_olesa[2] = int(meteo_dat_olesa[3][5:6])

    if meteo_dat_vacarisses[3][1:2] == chr(160):
        meteo_dat_vacarisses[2] = int(meteo_dat_vacarisses[3][3:4])
    elif meteo_dat_vacarisses[3][2:3] == chr(160):
        meteo_dat_vacarisses[2] = int(meteo_dat_vacarisses[3][4:5])
    else:
        meteo_dat_vacarisses[2] = int(meteo_dat_vacarisses[3][5:6])

    #En la dirección del viento, el el segundo char aparece como \xa0 (espacio vacío)#
    #por ejemplo en N (N\xa0), este espacio vacío se corresponde con el caracter chr(160)#
    if meteo_dat_olesa[3][1:2] == chr(160):
        meteo_dat_olesa[3] = meteo_dat_olesa[3][0:1]
    elif meteo_dat_olesa[3][2:3] == chr(160):
        meteo_dat_olesa[3] = meteo_dat_olesa[3][0:2]
    else:
        meteo_dat_olesa[3] = meteo_dat_olesa[3][:3]
    if meteo_dat_vacarisses[3][1:2] == chr(160):
        meteo_dat_vacarisses[3] = meteo_dat_vacarisses[3][0:1]
    elif meteo_dat_olesa[3][2:3] == chr(160):
        meteo_dat_vacarisses[3] = meteo_dat_vacarisses[3][0:2]
    else:
        meteo_dat_vacarisses[3] = meteo_dat_vacarisses[3][:3]
    meteo_dat_olesa[4] = int(meteo_dat_olesa[4][:4])
    meteo_dat_vacarisses[4] = int(meteo_dat_vacarisses[4][:4])
    meteo_dat_olesa[5] = float(meteo_dat_olesa[5][:3])
    if meteo_dat_vacarisses[5][2:3] == 'W':
        meteo_dat_vacarisses[5] = int(meteo_dat_vacarisses[5][:1])
    else:
        meteo_dat_vacarisses[5] = int(meteo_dat_vacarisses[5][:3])
    meteo_dat_vacarisses[6] = float(meteo_dat_vacarisses[6][:3])

    return meteo_dat_olesa + meteo_dat_vacarisses

####################
#  Programa (main) #
####################

#Creación listado de atributos del csv.
names = ['dia', 'hora', 'temp_olesa', '%_hum_olesa', 'wind_speed_olesa', 'dir_wind_speed_olesa', 'pressure_olesa', 'rain_olesa',
         'temp_vacarisses', '%_hum_vacarisses', 'wind_speed_vacarisses', 'dir_wind_speed_vacarisses', 'pressure_vacarisses',
         'radiation_vacarisses', 'rain_vacarisses']

#Creación del archivo csv. Se consulta al usuario si quiere inicializarlo, o seguir añadiendo datos.
sn = str(input("¿Desea inicializar el archivo CSV de captura de datos s/n? "))

if sn == 's':
    csvsalida = open('meteo_data.csv', 'w', newline='')
    salida = csv.writer(csvsalida)
    salida.writerow(names)
    del salida
    csvsalida.close()
else:
    pass

#Consulta al usuario sobre el número de ciclos horarios en los que quiere que se registren los datos.
ciclos = int(input("¿Durante cuántas horas quiere que se registren los datos? "))

#Abrimos de nuevo el archivo csv para continuar archivando datos de los nuevos ciclos horarios.
csvsalida = open('meteo_data.csv', 'a', newline='')
salida = csv.writer(csvsalida)

#En cada ciclo realizaremos una lectura en las estaciones.
for i in range(ciclos):
    #Unimos los listados de fecha y hora junto con los datos obtenidos de las estaciones mediante la función capture().
    data_register = [datetime.date.today(), datetime.datetime.now().hour] + captura()
    print(data_register)
    salida.writerow(data_register)
    #Con time.sleep aplazamos cada lectura 1 hora (3600 segundos).
    time.sleep(3600)

del salida
csvsalida.close()


from selenium import webdriver
from selenium.webdriver.common.keys import Keys

driver = webdriver.Firefox()
driver.get("http://www.google.com.sg")
assert "Python" in driver.title
elem = driver.find_element_by_name("q")
elem.send_keys("selenium")
elem.send_keys(Keys.RETURN)
assert "Google" in driver.title
driver.close()


import argparse
import csv
import re

def extractFeatureGtf(text, feature):
    regex = feature + ' "([^"]+)"'
    return re.search(regex, text).groups()[0]

def extractFeatureGff(text, feature):
    regex = feature + '=([^;]+);'
    return re.search(regex, text).groups()[0]

def printMatching(input):

    for row in csv.reader(open(input), delimiter='\t'):
        intronParent = extractFeatureGtf(row[8], "gene_id")
        domainParent = extractFeatureGff(row[17], "prot")
        if intronParent == domainParent:
            print("\t".join(row[0:9]))

def main():
    args = parseCmd()
    printMatching(args.input)

def parseCmd():

    parser = argparse.ArgumentParser(description='Parse output of bedtools \
                                     intersect between introns and domains and \
                                     print only introns which are from the same \
                                     gene as the protein domain they overlap.\
                                     The input is coming from a results of \"bedtools \
                                     intersect -a introns.gff -b domains.gff -s \
                                     -wa -wb > output\"')

    parser.add_argument('input',
                        help='Input file, output of bedtools intersect')

    return parser.parse_args()

if __name__ == '__main__':
    main()


from flask import Blueprint, request, jsonify
from fuzzywuzzy import fuzz

fuzzy_match_routes = Blueprint('fuzzy_match_routes', __name__)

@fuzzy_match_routes.route('/fuzzy_match', methods=['POST'])
def fuzzy_match():
    bands = request.json.get('bands')
    description = request.json.get('description')

    if not bands or not description:
        return jsonify({'error': 'Invalid request parameters'}), 400

    best_match = get_best_band_match(bands, description)

    return jsonify({'best_match': best_match}), 200

def get_best_band_match(bands, description):
    best_match = ''
    highest_score = 0

    for band in bands:
        score = fuzz.ratio(band.lower(), description.lower())
        if score > highest_score:
            highest_score = score
            best_match = band

    return best_match