#Script to transfer files from a (virtual) directory to another directory

import os
import csv
import time
import random
import shutil
import tempfile
from urllib.request import urlopen
from datetime import datetime

def checkConnection():
    #first establish that there is an internet connection, if not then pause script
    while not internetOn():
        print("Disconnected from internet, trying again in: ")
        for x in range (0,30):
            print(str(30 - x)+" seconds")
            time.sleep(1)

def fileSizeMatches(filesize_final, filesize_original):
    if filesize_final == filesize_original:
        return True
    return False

def internetOn(): #function to see if the internet is on
    listOfUrls = ["http://yahoo.com", "http://msn.com", "http://microsoft.com", "http://apple.com", "http://canon.com",
                  "http://disney.com", "http://netflix.com", "http://youtube.com", "http://twitter.com", "http://wikipedia.com"]
    siteToCheck = str(listOfUrls[random.randint(0,9)])
    print(siteToCheck)
    try:
        urlopen(siteToCheck, timeout=10)
        return True
    except:
        return False

def createTransferCSV(transferCSV, listOfTransfers):
    keys = ['name', 'original', 'target', 'filetype', 'filesize', 'status']
    with open(transferCSV, "w", newline="") as csv_file:
        writer = csv.DictWriter(csv_file, fieldnames=keys)
        writer.writeheader()  # will create first line based on keys
        for row in listOfTransfers: # turns the dictionaries into csv
            try:
                writer.writerow(row)
            except:
                print("could not write: " + str(row))

def getFileType(entry):
    e = os.path.splitext(entry)
    fileType = e[1]
    if fileType == "":
        fileType = "folder"
    return fileType

def mapTransfer(originalDir, targetDir, transferList):
    #iterate over all entries in directory
    for entry in os.listdir(originalDir):  # iterate over the original dir
        try:
            #entry = entry.replace(os.sep, '/')
            originalEntry = os.path.join(originalDir, entry)
            targetEntry = os.path.join(targetDir, entry)
            print("adding to map: " + originalEntry)
            fileType = getFileType(entry)  # split it to determine the file type
            fileSize = os.path.getsize(originalEntry) # get the filesize for verification purposes
            # append the dir/entry to the a dictionary entry with 'file type' matching the ext and 'status' : none
            transferList.append({"name": str(entry), "original": str(originalEntry), "target": str(targetEntry),
                                 "filetype": str(fileType), "filesize" : str(fileSize), "status": "awaiting transfer"})
            # if the entry is a folder, add it to the call mapTransfer
            if fileType == "folder":
                mapTransfer(originalEntry, targetEntry, transferList)
        except:
            checkConnection()
            failedLog = os.path.join(targetDir, 'failed.txt')
            with open(failedLog, 'a') as file:
                file.write(str(originalEntry + "\n"))
            continue

def transfer(name, original, target, originalSize, filetype):
    # elif see if the file has already been transfered
    if os.path.exists(target):
        targetFilesize = os.path.getsize(target)  # get the filesize for verification purposes
        if fileSizeMatches(str(originalSize), str(targetFilesize)):  # if it matches then write the row
            print("already transferred | {}".format(name))
            return 'done'
        elif filetype == "folder":
            return 'incomplete'
    # else try to Transfer
    # if it's a folder, create the directory
    if filetype == 'folder':
        os.makedirs(str(target))
        return 'done'
    # else attempt to transfer from 'original' to 'target'
    else:
        print("attempting transfer | {}".format(name))
        shutil.copyfile(str(original), str(target))
        # verify that the filesize matches
        filesize = os.path.getsize(target)
        if fileSizeMatches(str(originalSize), str(filesize)):  # if it matches then write the row
            print("succesfully transffered | {}".format(name))
            return 'done'
        else:
            return 'failed'

def transferFiles(transferCSV, transferDir, retryFailed):
    #iterate over rows on csv
    errors = []
    tempCSVFile = os.path.join(transferDir, "temp.csv")
    #simulatenously create new (temp) csv that will overwrite current csv
    with open(transferCSV, newline='') as csv_file, open(tempCSVFile, 'w', newline='') as temp_csv:
        reader = csv.DictReader(csv_file)
        finishedLog = csv.DictWriter(temp_csv, fieldnames=['name', 'original', 'target', 'filetype', 'filesize', 'status'])
        finishedLog.writeheader()  # will create first line based on keys
        for row in reader:
            #setup temp row
            tempRow = {'name' : row['name'], 'original' : row['original'], 'target' : row['target'],
                'filetype' : row['filetype'], 'filesize' : row['filesize'], 'status' : row['status']}
            #check the status of the entry
            #if it's done, marked skip, or failed = False -first- add row to temp csv -then- continue to next entry
            if not retryFailed and ['status'] == 'failed' or row['status'] == "done":
                print("skipping: {} | status: {}".format(row['name'], row['status']))
                finishedLog.writerow(row)  # write row to temp file
                continue

            #transfer the files
            try:
                tempRow['status'] = transfer(str(row['name']), str(row['original']),
                                         str(row['target']), str(row['filesize']), str(row['filetype']))

            except(IOError, os.error) as why:
                errors.append((row['original'], str(why)))
                #except - add row to temp csv with 'failed' as status
                tempRow['status'] = 'failed'
                print("FAILED | {}".format(row['name']))
            finishedLog.writerow(tempRow)
    #overwrite old csv with new csv
    shutil.move(tempCSVFile, transferCSV)
    if errors:
        for err in errors:
            print(err)

def readLog(transferCSV):
    count = {"total" : 0, "done" : 0, "failed": 0}
    with open(transferCSV, 'r',) as CSVlog:
        log = csv.DictReader(CSVlog)
        for row in log:
            count['total'] += 1
            if row['status'] == "done":
                count["done"] += 1
            if row['status'] == "failed":
                count['failed'] +=1
    return count

def main(map, transfer):
    startTime = datetime.now()
    transferList = []
    originalDir = r'V:/'
    transferDir = r'F:/VirtualDrives/share'
    transferCSV = os.path.join(transferDir, "transferLog.csv")
    if map:
        mapTransfer(originalDir, transferDir, transferList)
        print("{} dir entries added to map...".format(len(transferList)))
        createTransferCSV(transferCSV, transferList)
    if transfer:
        transferFiles(transferCSV, transferDir, retryFailed=True)
        print("file transfer finished...results written to log")
        count = readLog(transferCSV)
        print("------------------------------------------")
        print(str(round(count['done']/count['total']*100)) + "% transferred")
        print("{} files attempted | {} completed successfully | {} failed".format(count['total'], count['done'], count['failed']))
    endTime = datetime.now() - startTime
    print("time elapsed: {}".format(endTime))

main(map=True, transfer=True)


import os
import torch
import numpy as np
from osgeo import gdal, ogr
import geopandas as gpd
from shapely.geometry import Point
from tqdm import tqdm
from matplotlib import pyplot as plt
import affine
import rasterio
import rasterio.features
import random

def list_paths_in_dir(root_dir, file_endings=None):
    """
    Get a list of paths to files within a directory and subdirectories

    :param root_dir: directory to search
    :param file_endings: list of file endings to consider. Default is all
    """
    files = []
    for dirpath, _, filenames in os.walk(root_dir):
        if file_endings:
            filenames = [f for f in filenames if f.endswith(file_endings)]
        for filename in filenames:
            files.append(os.path.join(dirpath, filename))
    return files

def build_padded_vrt(sources, padding=1000):
    """ Creates an inmemory gdal VRT from a list of sources with additional padding
    :param sources: list of paths to raster files
    :param padding: amount of padding in meters around the vrt to avoid read errors
    :returns: a gdal vrt object
    """
    vrt = gdal.BuildVRT('', sources)
    extent = get_raster_extent(vrt)
    extent = extent[0] - padding, extent[3] - padding, extent[2] + padding, extent[1] + padding
    vrt = gdal.BuildVRT('', sources, options=gdal.BuildVRTOptions(outputBounds=extent, resolution='highest'))
    return vrt

def get_all_bands_as_numpy(raster, offset=(0, 0), res=None, bands=None, means=None, stds=None):
    """
    Fetches bands from a raster, stacks them and returns normalised numpy array.
    A list of means and standard deviations the same length as the number of bands can be passed to standardise each
    one individually. To standardise all bands with the same mean and std pass a list of length 1

    :param raster: gdal raster object
    :param offset: offset tuple (x,y) in pixels from top left corner
    :param res: output size in pixels (crops input). Default is to use the whole raster.
    :param bands: list of bands to extract. Default is all.
    :param means: list of means for each band to standardise
    :param stds: list of standard deviations for each band to standardise
    :return: standardised numpy array
     """

    if res is None:
        res = (raster.RasterXSize, raster.RasterYSize)
    else:
        res = (res, res)

    if bands is None:
        bands = range(1, raster.RasterCount + 1)

    # standardise all bands the same if only one value is given
    if means and len(means) == 1:
        means = raster.RasterCount * means
    if stds and len(stds) == 1:
        stds = raster.RasterCount * stds

    band_list = []
    i = 0
    for band in bands:
        rb = raster.GetRasterBand(band)
        rb.SetNoDataValue(means[i] if means else 0)
        arr = rb.ReadAsArray(offset[0], offset[1], res[0], res[1], buf_type=gdal.GDT_Float32)

        # Standardise
        if means:
            arr -= means[i]
        if stds:
            arr /= stds[i]

        band_list.append(arr)
        arr = None
        rb = None
        i += 1

    image = np.stack(band_list, axis=2)

    return image

def redistribute_satellite_data(image):
    """ Redistributes the input data to spread it more evenly.

    The input data was found to bi bimodal with a large spike on the negative side of the standardised data
    from the shadows. Multiplying negative values by 3 as well as by themselves was found to result in a better spread
    of data.
    """
    image[image < 0] = -3 * image[image < 0] ** 2
    return image

def undo_redistribute_satellite_data(image):
    """ Reverses effects of redistribute_satellite_data for visualisation"""
    image[image < 0] = -torch.sqrt(-image[image < 0]/3)
    return image

def get_numpy_from_ogr_shapefile(shapefile, ref_raster, offset=(0, 0), res=None):
    """
    Rasterises shapefile and return numpy array. The shapefile should be opened with ogr.

    :param shapefile: shapefile openened with ogr
    :param ref_raster: reference raster which shapefile will be projected on.
    :param offset: offset (x,y) in pixels from the top left corner.
    :param res: output size (x, y) in pixels. Output will be cropped to this. Default is size of reference raster.
    :return: numpy array of shapefile at specified window
    """

    if res is None:
        res = (ref_raster.RasterXSize, ref_raster.RasterYSize)

    # get spatial information from reference raster
    geo_transform = ref_raster.GetGeoTransform()
    pixel_w = geo_transform[1]  # pixel width eg. 1 pixel => 1.5m
    x_min = geo_transform[0]
    y_max = geo_transform[3]
    y_min = y_max + geo_transform[5] * ref_raster.RasterYSize
    y_res = ref_raster.RasterYSize

    # prepare raster
    shape_raster = gdal.GetDriverByName('MEM').Create('', res[0], res[1], 1, gdal.GDT_Byte)
    shape_raster.SetGeoTransform(
        (x_min + offset[0] * pixel_w, pixel_w, 0, y_min + (y_res - offset[1]) * pixel_w, 0, -pixel_w))

    # rasterise
    shape_l = shapefile.GetLayer()
    err = gdal.RasterizeLayer(shape_raster, [1], shape_l, burn_values=[1])
    if err != 0:
        print('Rasterising error: ', err)

    return shape_raster.ReadAsArray()

def rasterise_geopandas(dataset, tile_size, offset, burn_val=1, res=1.5, individual=False):
    """
    Rasterise geopandas dataset and return numpy array

    :param dataset: geopandas dataset to be rasterised
    :param tile_size: size of the output patch
    :param offset: position of top left corner. Make sure this is in the correct CRS
    :param burn_val: value to write where there is a shape. Background is zero. If string, it is interpreted as an attribute
    :param res: resolution of raster (meters per pixel)
    :param individual: whether to rasterise each avalanche into a separate channel
    """
    transform = affine.Affine(res, 0, offset[0], 0, -res, offset[1])

    # first get subset of avalanches intersecting patch before rasterising them
    subset = dataset.cx[offset[0]: offset[0] + res * tile_size[0], offset[1] + -res * tile_size[1]: offset[1]:]
    if len(subset) == 0:
        return np.zeros(tile_size)

    if isinstance(burn_val, str):
        shapes = ((sample.geometry, sample[burn_val]) for _, sample in subset.iterrows())
    else:
        shapes = ((geom, value) for geom, value in zip(subset.geometry, len(subset) * [burn_val]))

    raster = []
    if individual:
        for shape in shapes:
            raster.append(rasterio.features.rasterize([shape], tile_size, transform=transform, dtype=np.single))
        raster = np.stack(raster, axis=2)
    else:
        raster = rasterio.features.rasterize(shapes, tile_size, transform=transform, dtype=np.single)
    return raster

def get_raster_extent(raster):
    """
    Get the spatial extent of the raster in its coordinate frame
    :param raster: gdal raster object
    :return: ulx, uly, lrx, lry whereby ul is upper left and lr is lower right in meters
    """
    ulx, xres, xskew, uly, yskew, yres = raster.GetGeoTransform()
    lrx = ulx + (raster.RasterXSize * xres)
    lry = uly + (raster.RasterYSize * yres)
    return ulx, uly, lrx, lry

def labels_to_mask(labels):
    """ Convert image labels to mask of zeros and ones. Returns binary mask of avalanches ignoring their certainty"""
    return (labels != 0).float()

def get_avalanches_in_region(avalanches, region):
    """
    Return geoseries of all avalanches within the region
    """
    selection = avalanches.intersects(region.geometry.loc[0])
    for i in range(1, len(region)):
        selection |= avalanches.intersects(region.geometry.loc[i])

    return avalanches[selection]

def generate_point_grid(region, tile_size, pixel_w ,overlap=0):
    """
    Generate a geopandas Geoseries object of coordinates within a region
    corresponding to the top left corner of the corrsponding patch.

    The region is expanded before checking whether points are within it such that
    it is completely covered by the points.

    :param region: shapefile opened with geopandas
    :param tile_size: size that patches will be in meters
    :param overlap: overlap of tiles in meters
    :return: geopandas geoseries of coordinates as Points
    """

    spacing = tile_size*pixel_w - overlap

    # generate uniform grid over the entire extent

    minx, miny, maxx, maxy = region.buffer(tile_size, join_style=2).total_bounds
    #minx, miny, maxx, maxy = region.total_bounds
    X, Y = np.mgrid[minx:maxx + spacing:spacing, miny:maxy + spacing:spacing]
    X, Y = X.ravel(), Y.ravel()
    points = gpd.GeoSeries(map(Point, zip(X, Y)))

    # slightly expand regions and filter out points outside of polygons
    expanded_region = region.buffer(tile_size*pixel_w/2, join_style=2)
    mask = points.within(expanded_region.loc[0])
    for i in range(1, len(expanded_region)):
        mask |= points.within(expanded_region.loc[i])
    points = points.loc[mask]

    # shift coordinates such that they are in the top left of each patch
    points = points.translate(xoff=-tile_size*pixel_w*1/2, yoff=tile_size*pixel_w*1/2)

    return points

def generate_sample_points(avalanches, region, tile_size, no_aval_ratio=0.05, n=200):
    """ Intelligently choose samples such that there is no overlap but large avalanches are covered
        Also add samples with no avalanche present

        :param avalanches: geopandas geoseries of avalanche polygons
        :param region: region in which samples are contained as geoseries
        :param tile_size: size of one sample
        :param no_aval_ratio: ratio of samples to add with no avalanche [0-1]
        :param n: number of neighbour avalanches (in geoseries order) to consider when checking distance
        :returns: geoseries of sample Points
    """
    sample_points = gpd.GeoSeries()

    # add point for each avalanche, multiple points for large avalanches
    for i in tqdm(range(0, len(avalanches)), desc="Generating Points in avalanches"):
        aval = avalanches.iloc[i]
        diff = aval.geometry
        while not diff.is_empty:
            # get point inside avalanche - not random but not centroid either
            p = diff.representative_point()

            # get difference of avalanche and square around sample point
            diff = diff.difference(p.buffer(tile_size, cap_style=3))

            # only add point if it is not too close to another (could be too close to another avalanche)
            if not (sample_points.iloc[-n:].distance(p) < tile_size).any():
                sample_points = sample_points.append(gpd.GeoSeries(p))

    # add points with no avalanche
    for i in tqdm(range(0, int(no_aval_ratio * len(avalanches))), desc='Generating points with no avalanches'):
        for j in range(0, 100): # max 100 tries
            p = get_random_point_in_region(region)

            # only add point if far enough from all avalanches
            if not (sample_points.distance(p) < tile_size).any():
                sample_points = sample_points.append(gpd.GeoSeries(p))
                break

    return sample_points

def get_random_point_in_region(region):
    """ Sample a random point within a region
        :param region: geoseries with one or multiple polygons
        :returns: random point within region
    """
    minx, miny, maxx, maxy = region.total_bounds
    while True:
        p = Point(random.uniform(minx, maxx), random.uniform(miny, maxy))
        if region.contains(p).any():
            return p


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

from odoo import models, fields, api

import pdb;

class AccountPaymentPersonalization(models.Model):
    _inherit = "account.payment"

    sale_order_id = fields.Many2one('sale.order')

    @api.multi
    def action_validate_invoice_payment(self):
        """
            Si la factura queda totalmente pagada y no se ha validado el albarán de salida lo confirma
        """
        inv = self.env['account.invoice'].browse(self._context.get('active_id'))
        if inv.residual - self.amount == 0:
            pv_name = inv.origin
            if pv_name:
                stock_picking = self.env['stock.picking'].search([('origin', '=', pv_name),
                                                                  ('state', 'not in', ['done', 'cancel'])])
                for picking in stock_picking:
                    for line in picking.move_lines:
                        line.quantity_done = line.product_uom_qty
                        line.env.cr.commit()
                    picking.button_validate()
        return super(AccountPaymentPersonalization, self).action_validate_invoice_payment()


# -*- coding: utf-8 -*-
"""
Created on Wed Jul 3 13:16:37 2019

@author: Lee
"""
import requests
import time
import hashlib
import random

class youdao_crawl():
    def __init__(self):
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36',
            'Referer': 'http://fanyi.youdao.com/',
            'Cookie': 'OUTFOX_SEARCH_USER_ID=850665018@10.169.0.83; OUTFOX_SEARCH_USER_ID_NCOO=71221285.04687975; _ntes_nnid=6f09e5c54e440a52f10b177100aa9d1d,1561431366198; JSESSIONID=aaavC3vS98F0m-IjbuAVw; DICT_UGC=be3af0da19b5c5e6aa4e17bd8d90b28a|; JSESSIONID=abc32dNbypRD-5CwnJAVw; user-from=http://www.youdao.com/w/eng/%E8%8B%B9%E6%9E%9C/; from-page=http://www.youdao.com/w/eng/%E8%8B%B9%E6%9E%9C/; ___rl__test__cookies=1562740161910'
          }
        self.data = {
            'i': None,
            'from': 'AUTO',
            'to': 'AUTO',
            'smartresult': 'dict',
            'client': 'fanyideskweb',
            'salt': None,
            'sign': None,
            'ts': None,
            'bv': None,
            'doctype': 'json',
            'version': '2.1',
            'keyfrom': 'fanyi.web',
            'action': 'FY_BY_REALTlME'
          }
        self.url = 'http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule'

    def translate(self, word):
        ts = str(int(time.time()*10000))
        salt = str(int(time.time()*10000) + random.random()*10 + 10)
        sign = 'fanyideskweb' + word + salt + '97_3(jkMYg@T[KZQmqjTK'
        sign = hashlib.md5(sign.encode('utf-8')).hexdigest()
        bv = '5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36'
        bv = hashlib.md5(bv.encode('utf-8')).hexdigest()
        self.data['i'] = word
        self.data['salt'] = salt
        self.data['sign'] = sign
        self.data['ts'] = ts
        self.data['bv'] = bv
        re = requests.post(self.url, headers=self.headers, data=self.data)
        return re.json()['translateResult'][0][0].get('tgt')

if __name__ == '__main__':
    youdao = youdao_crawl()
    while True:
        content = input("请输入您需要翻译的内容:")
        if content == "q":
            break
        trans = youdao.translate(content)
        print(trans)


from pyfirmata import Arduino
import time
from bottle import route, run, template, static_file, response

arduino_presente = True
POSICAO_INICIAL = 90

# tempo em segundos permitido entre envio de comandos ao Arduino
tempo_entre_execucoes = 10

try:
    # Tenta conexão com um Arduino que esteja usando o código StandardFirmata
    board = Arduino('/dev/ttyACM0')   # define porta usada pelo Arduino
    servo1 = board.get_pin('d:9:s')   # conecta servo no pino 9
    servo2 = board.get_pin('d:10:s')  # conecta servo no pino 10
    servo1.write(POSICAO_INICIAL)
    servo2.write(POSICAO_INICIAL)

except:
    print("Iniciando sem um arduino")
    arduino_presente = False

hora_ultima_execucao = time.time()

def posicionaServos(esquerda,direita):
    if arduino_presente:
        servo1.write(int(esquerda))
        servo2.write(int(direita))
    else:
        print("ServoDir: %d" % direita)
        print("ServoEsq: %d" % esquerda)

@route('/static')
@route('/static/<filename:path>')
def send_static(filename="index.html"):
    return static_file(filename, root='./html')

@route('/bracos/<pos>')
def index(pos):
    print("Recebi %s" % pos)
    pos1, pos2 = pos.split(",")
    try:
        posEsq, posDir = int(pos1),int(pos2)
    except ValueError:
        response.status = 400
        return '''Argumentos inválidos: %s
                  Informe dois inteiros entre vírgulas.
                  ex: http://servidor:porta/12,95''' % pos
    global hora_ultima_execucao
    hora_atual = time.time()
    if hora_atual - hora_ultima_execucao > tempo_entre_execucoes:
        posicionaServos(posEsq,posDir)
        timestamp = time.time()
    else:
        response.status = 202
        return "Espere %d segundos" % int(tempo_entre_execucoes
                                          - (hora_atual
                                          - hora_ultima_execucao)
                                          )
    return {'esquerda': posEsq, 'direita': posDir}

run(host='0.0.0.0', port=8080, reloader=True)


import numpy as np

""" A controller class which implements a task space force feedback and feedforward
controller with velocity compensation."""

class Control:

    def __init__(self, arm):

        # Bind arm
        self.arm = arm

        # Control type
        self.control_type = 'task'

        # Task space trajectory
        self.trajectory_available = False
        self.ts_trajectory_x = None
        self.ts_trajectory_dx = None
        self.ts_trajectory_ddx = None

        # External force function
        self.external_force_available = False
        self.fext_function = None

        # Control parameters
        self.kp = 1
        self.ki = 0
        self.eint = 0
        self.k_damp = 25

        # Desired force
        self.f_des = np.zeros((6, 1))

        # Previously executed joint torques
        self.tau_prev = np.zeros((self.arm.DOF, 1))

    def set_force_target(self, f_des):
        ''' Sets a force target directly'''
        assert np.shape(f_des) == (6, 1)
        self.f_des = f_des

    def control(self):
        ''' Implements the control law'''

        # Get end-effector Jacobian
        J = self.arm.generate_jacobian_ee(self.arm.q)

        # Calculate force the robot is applying in task space
        f_tip = np.dot(np.linalg.pinv(J).T, self.tau_prev)

        # Wall simulated at x=1.2m, if robot didn't reach wall, there is no external force
        # , otherwise, external force equals applied force
        if np.resize(self.arm.forward_kinematics(self.arm.q), (2,))[0] >= 1.2:
            f_ext = -f_tip
        else:
            f_ext = np.array([[0.0], [0.0], [0.0], [0.0], [0.0], [0.0]])

        # Error
        e_ = self.f_des - f_ext
        self.eint = self.eint + e_ * self.arm.dt

        # Feedback torques in task space
        f_p = self.kp * e_
        f_i = self.ki * self.eint
        f_fb = f_p + f_i

        # Feedforward torque in task space
        f_ff = self.f_des

        # Velocity compensation
        ee_twist = np.dot(J, self.arm.dq)
        vel_comp = np.reshape(self.k_damp * ee_twist, (6, 1))

        # Total torque
        f = f_ff + f_fb - vel_comp

        # Take only x-direction
        f[1] = 0
        f[-1] = 0

        # Compensation terms
        grav = self.arm.gravity()

        # Compute desired torque with only gravity compensation
        tau = np.dot(J.T, f) + grav
        self.tau_prev = tau

        # Limit commanded torques
        tau = np.clip(tau, -self.arm.tau_limit, self.arm.tau_limit)

        return e_, f_p, f_i, np.zeros((6, 1)), tau


"""
tablet.py – Flatland binds a Canvas instance in the Flatland Application domain to a Tablet instance
in the drawing domain. The Tablet can be drawn using cairo or some other graphics drawing framework.
"""
import logging
from flatland.flatland_exceptions import NonSystemInitialLayer, TabletBoundsExceeded
from flatland.datatypes.geometry_types import Rect_Size, Position
import cairo
from flatland.drawing_domain.styledb import StyleDB
from flatland.drawing_domain.layer import Layer
from typing import Optional

class Tablet:
    """
    The Tablet class is part of the drawing_domain which provides a service to the Flatland model
    diagram application. We can imagine a virtual Tablet that the application uses to draw
    all of its nodes and connectors and other model elements. The Tablet abstracts away the details
    of drawing and graphics library interaction from the fundamental Flatland application.

    For example, when a Flatland node compartment wants to draw itself, it doesn't worry about line
    widths, dash patterns and colors. It also doesn't worry about flipping to whatever coordinate
    system the graphics library uses. A compartment would just say "add_shape( asset, size, location )" and
    the Tablet will take care of the rest. Here the asset is the name of the model entity 'compartment',
    the size in points (or whatever units the Flatland app wants to use) and location expressed in Flatland
    Canvas coordinates.

    When a blank Flatland Canvas is created, it will initialize its underlying Tablet and select a predefined
    drawing Type ('class diagram', 'state machine diagram' etc.) and Presentation Style ('default', 'formal',
    'diagnostic', etc).  The Diagram Type determines what kinds of things might be drawn, Assets such as 'connector',
    'compartment', 'class name', etc., while the Presentation Style establishes the Text and Line Styles to
    be used when drawing those Assets. All of this information is stored in a database which the Tablet
    loads upon creation.

    Each time the application wants something drawn, it will ask Tablet to add the appropriate Asset
    to one of its draw lists. A separate list is maintained for all the rectangles, line segments and
    text lines to be rendered. When the application is finished populating these lists, the Tablet can
    render everything using its graphic library, such as Cairo, using whatever coordinate system the
    library supplies, making any necessary conversions from the application coordinate system.

    Consequently, a new graphics library, such as NumPy for example, can be supported by updating the drawing_domain
    and primarily the Tablet class without having to make any changes in the Flatland application. Futhermore, any
    changes to text styles, colors, line patterns etc can be perfomred by updating the drawing_domain's style
    database.

        Attributes

        - Size -- The size of the whatever surface (PDF, RGB, SVG, etc) Tablet supports.
        - Output_file -- A filename or output stream object to be output as a drawing
        - PDF_sheet -- For now we only support PDF output as defined in Cairo.  So this is a Cairo surface
        - Context -- A Cairo context object for drawing on the PDF sheet
    """

    def __init__(self, size: Rect_Size, output_file, drawing_type: str, presentation: str, layer: str):
        """
        Constructs a new Tablet instance
        :param size: Vertical and horizontal span of the entire draw surface in points
        :param output_file: Name of the drawing file to be generated, PDF only for now
        :param drawing_type: Type of drawing so we can determine what kinds text and graphics can be drawn
        :param presentation: The layer's Presentation to load
        :param layer: The initial layer to be created on this Tablet (usually 'diagram')
        """
        self.logger = logging.getLogger(__name__)

        # Load all of the common font, color, etc. styles used by all Presentations from the
        # Flatland DB
        StyleDB()

        # Establish a system default layer ordering. Not all of them will be used in any given
        # Drawing, but this is the draw order from bottom-most layer upward
        # It can (should be) customizable by the user, but this should work for most diagrams
        self.layer_order = ['sheet', 'grid', 'frame', 'diagram', 'scenario', 'annotation']
        self.Presentations = {}  # Presentations loaded from the Flatland database, updated by Layer class

        if layer not in self.layer_order:
            raise NonSystemInitialLayer
        self.layers = {layer: Layer(name=layer, tablet=self, presentation=presentation, drawing_type=drawing_type)}
        # Initialize the first layer at the indicated position. If the position is not in the system layer order
        # list, it will be placed as the topmost layer. Usually, though, the initial layer should be diagram

        self.Drawing_type = drawing_type  # class diagram, state diagram, etc
        self.Size = size
        self.Output_file = output_file
        self.PDF_sheet = cairo.PDFSurface(self.Output_file, self.Size.width, self.Size.height)
        self.Context = cairo.Context(self.PDF_sheet)

    def add_layer(self, name: str, presentation: str, drawing_type: str, fill: str = None) -> Optional[Layer]:
        """Add a new layer if not already instantiated and return it"""
        if not self.layers.get(name):
            if name not in self.layer_order:
                self.layer_order.append(name)
            self.layers[name] = Layer(name=name, tablet=self, presentation=presentation, drawing_type=drawing_type,
                                      fill=fill)
            return self.layers[name]
        else:
            self.logger.warning(f"Layer: [{name}] previously instantiated")
            return None

    def render(self):
        """
        Renders each instantiated layer of the Tablet moving up the z axis. Any uninstantiated layers are skipped.
        """
        [self.layers[name].render() for name in self.layer_order if self.layers.get(name)]

    def to_dc(self, tablet_coord: Position) -> Position:
        """
        To display coordinates – Convert tablet bottom_left origin coordinate to
        display coordinate where top-left origin is used.
        """
        if tablet_coord.y > self.Size.height:
            raise TabletBoundsExceeded
        assert tablet_coord.x >= 0, "Negative x value"
        assert tablet_coord.y >= 0, "Negative y value"
        return Position(x=tablet_coord.x, y=self.Size.height - tablet_coord.y)

    def __repr__(self):
        return f'Size: {self.Size}, Dtype: {self.Drawing_type},' \
               f'Output: {self.Output_file}'


class functions:
        def d1(self):
            conn = sqlite3.connect('./db.sqlite3')
            c_select = conn.cursor()
            final = c_select.execute("SELECT * FROM article where stock != 'stock' order by date desc , time desc")
            df = pd.DataFrame(final)
            df.columns= ['id', 'date' , 'time' , 'title', 'press' ,'stock' , 'posi_nega' ]
            result = df
            return result;


# -*- coding: utf-8 -*-
"""
                         +----------------+
                         | BasePackageRef |
                         +-------+--------+
                                 |
              +------------+     |     +-----------------+
              | PackageRef <-----+-----> IndexJsonRecord |
              +------+-----+           +-------+---------+
                     |                         |
                     +-----------+-------------+
                                 |
                         +-------v-------+
                         | PackageRecord |
                         +--+---------+--+
+--------------------+      |         |      +--------------+
| PackageCacheRecord <------+         +------> PrefixRecord |
+--------------------+                       +--------------+

"""
from __future__ import absolute_import, division, print_function, unicode_literals

from .channel import Channel
from .enums import FileMode, LinkType, NoarchType, PackageType, PathType, Platform
from .._vendor.auxlib.entity import (BooleanField, ComposableField, DictSafeMixin, Entity,
                                     EnumField, IntegerField, ListField, NumberField,
                                     StringField)
from ..base.context import context
from ..common.compat import isiterable, itervalues, string_types, text_type

class LinkTypeField(EnumField):
    def box(self, instance, instance_type, val):
        if isinstance(val, string_types):
            val = val.replace('-', '').replace('_', '').lower()
            if val == 'hard':
                val = LinkType.hardlink
            elif val == 'soft':
                val = LinkType.softlink
        return super(LinkTypeField, self).box(instance, instance_type, val)

class NoarchField(EnumField):
    def box(self, instance, instance_type, val):
        return super(NoarchField, self).box(instance, instance_type, NoarchType.coerce(val))

class TimestampField(NumberField):

    # @staticmethod
    # def _make_seconds(val):
    #     if val:
    #         val = int(val)
    #         if val > 253402300799:  # 9999-12-31
    #             val //= 1000  # convert milliseconds to seconds; see conda/conda-build#1988
    #     return val

    @staticmethod
    def _make_milliseconds(val):
        if val:
            if val < 253402300799:  # 9999-12-31
                val *= 1000  # convert seconds to milliseconds
            val = int(val)
        return val

    def box(self, instance, instance_type, val):
        return self._make_milliseconds(
            super(TimestampField, self).box(instance, instance_type, val)
        )

    def unbox(self, instance, instance_type, val):
        return self._make_milliseconds(
            super(TimestampField, self).unbox(instance, instance_type, val)
        )

    def dump(self, instance, instance_type, val):
        return self._make_milliseconds(
            super(TimestampField, self).dump(instance, instance_type, val)
        )

class Link(DictSafeMixin, Entity):
    source = StringField()
    type = LinkTypeField(LinkType, required=False)

EMPTY_LINK = Link(source='')

class _FeaturesField(ListField):

    def __init__(self, **kwargs):
        super(_FeaturesField, self).__init__(string_types, **kwargs)

    def box(self, instance, instance_type, val):
        if isinstance(val, string_types):
            val = val.replace(' ', ',').split(',')
        return super(_FeaturesField, self).box(instance, instance_type, val)

    def dump(self, instance, instance_type, val):
        if isiterable(val):
            return ' '.join(val)
        else:
            return val or ()  # default value is (), and default_in_dump=False

class ChannelField(ComposableField):

    def __init__(self, aliases=()):
        self._type = Channel
        super(ComposableField, self).__init__(required=False, aliases=aliases)

    def dump(self, instance, instance_type, val):
        if val:
            return text_type(val)
        else:
            val = instance.channel  # call __get__
            return text_type(val)

    def __get__(self, instance, instance_type):
        try:
            return super(ChannelField, self).__get__(instance, instance_type)
        except AttributeError:
            url = instance.url
            return self.unbox(instance, instance_type, Channel(url))

class SubdirField(StringField):

    def __init__(self):
        super(SubdirField, self).__init__(required=False)

    def __get__(self, instance, instance_type):
        try:
            return super(SubdirField, self).__get__(instance, instance_type)
        except AttributeError:
            try:
                url = instance.url
            except AttributeError:
                url = None
            if url:
                return self.unbox(instance, instance_type, Channel(url).subdir)

            try:
                platform, arch = instance.platform.name, instance.arch
            except AttributeError:
                platform, arch = None, None
            if platform and not arch:
                return self.unbox(instance, instance_type, 'noarch')
            elif platform:
                if 'x86' in arch:
                    arch = '64' if '64' in arch else '32'
                return self.unbox(instance, instance_type, '%s-%s' % (platform, arch))
            else:
                return self.unbox(instance, instance_type, context.subdir)

class FilenameField(StringField):

    def __init__(self, aliases=()):
        super(FilenameField, self).__init__(required=False, aliases=aliases)

    def __get__(self, instance, instance_type):
        try:
            return super(FilenameField, self).__get__(instance, instance_type)
        except AttributeError:
            try:
                url = instance.url
                fn = Channel(url).package_filename
                if not fn:
                    raise AttributeError()
            except AttributeError:
                fn = '%s-%s-%s' % (instance.name, instance.version, instance.build)
            assert fn
            return self.unbox(instance, instance_type, fn)

class PathData(Entity):
    _path = StringField()
    prefix_placeholder = StringField(required=False, nullable=True, default=None,
                                     default_in_dump=False)
    file_mode = EnumField(FileMode, required=False, nullable=True)
    no_link = BooleanField(required=False, nullable=True, default=None, default_in_dump=False)
    path_type = EnumField(PathType)

    @property
    def path(self):
        # because I don't have aliases as an option for entity fields yet
        return self._path

class PathDataV1(PathData):
    # TODO: sha256 and size_in_bytes should be required for all PathType.hardlink, but not for softlink and directory  # NOQA
    sha256 = StringField(required=False, nullable=True)
    size_in_bytes = IntegerField(required=False, nullable=True)
    inode_paths = ListField(string_types, required=False, nullable=True)

    sha256_in_prefix = StringField(required=False, nullable=True)

class PathsData(Entity):
    # from info/paths.json
    paths_version = IntegerField()
    paths = ListField(PathData)

class BasePackageRef(DictSafeMixin, Entity):
    name = StringField()
    version = StringField()
    build = StringField(aliases=('build_string',))
    build_number = IntegerField()

class PackageRef(BasePackageRef):
    # the canonical code abbreviation for PackageRef is `pref`
    # fields required to uniquely identifying a package

    channel = ChannelField(aliases=('schannel',))
    subdir = SubdirField()
    fn = FilenameField(aliases=('filename',))

    md5 = StringField(default=None, required=False, nullable=True, default_in_dump=False)
    url = StringField(default=None, required=False, nullable=True, default_in_dump=False)

    @property
    def schannel(self):
        return self.channel.canonical_name

    @property
    def _pkey(self):
        return self.channel.canonical_name, self.subdir, self.name, self.version, self.build

    def __hash__(self):
        return hash(self._pkey)

    def __eq__(self, other):
        return self._pkey == other._pkey

    def dist_str(self):
        return "%s::%s-%s-%s" % (self.channel.canonical_name, self.name, self.version, self.build)

class IndexJsonRecord(BasePackageRef):

    arch = StringField(required=False, nullable=True)  # so legacy
    platform = EnumField(Platform, required=False, nullable=True)  # so legacy

    depends = ListField(string_types, default=())
    constrains = ListField(string_types, default=())

    track_features = _FeaturesField(required=False, default=(), default_in_dump=False)
    features = _FeaturesField(required=False, default=(), default_in_dump=False)

    subdir = SubdirField()
    # package_type = EnumField(NoarchType, required=False)  # previously noarch
    noarch = NoarchField(NoarchType, required=False, nullable=True, default=None,
                         default_in_dump=False)  # TODO: rename to package_type
    preferred_env = StringField(required=False, nullable=True, default=None, default_in_dump=False)

    license = StringField(required=False)
    license_family = StringField(required=False)

    timestamp = TimestampField(required=False)

    @property
    def combined_depends(self):
        from .match_spec import MatchSpec
        result = {ms.name: ms for ms in (MatchSpec(spec) for spec in self.depends or ())}
        result.update({ms.name: ms for ms in (MatchSpec(spec, optional=True)
                                              for spec in self.constrains or ())})
        return tuple(itervalues(result))

class PackageRecord(IndexJsonRecord, PackageRef):
    # the canonical code abbreviation for PackageRecord is `prec`, not to be confused with
    # PackageCacheRecord (`pcrec`) or PrefixRecord (`prefix_rec`)
    #
    # important for "choosing" a package (i.e. the solver), listing packages
    # (like search), and for verifying downloads
    #
    # this is the highest level of the record inheritance model that MatchSpec is designed to
    # work with

    date = StringField(required=False)
    size = IntegerField(required=False)

    package_type = EnumField(PackageType, required=False, nullable=True)

    def __str__(self):
        return "%s/%s::%s==%s=%s" % (self.channel.canonical_name, self.subdir, self.name,
                                     self.version, self.build)

IndexRecord = PackageRecord


class Solution:
    def shortestPathBinaryMatrix(self, grid: List[List[int]]) -> int:
        n, q = len(grid), deque([(0,0,1)])
        d = [(1,0),(-1,0),(0,1),(0,-1),(1,1),(-1,-1),(-1,1),(1,-1)]

        if grid[0][0] == 1 or grid[n - 1][n - 1] == 1:
            return -1

        grid[0][0] = 1

        while q:
            x, y, dist = q.popleft()

            if (x,y) == (n - 1, n-1):
                return dist

            for nx, ny in d:
                i, j = x + nx, y + ny

                if 0 <= i < n and 0 <= j < n and grid[i][j] == 0:
                    q.append((i, j, dist + 1))
                    grid[i][j] = 1

        return -1


import math
import random

def leibniz_formula(precision=1e-5):
    pi = 0.
    delta = 999
    k = 0
    while delta > precision:
        delta = (4. if k % 2 == 0 else -4.) / (2 * k + 1)
        pi += delta
        k += 1
    return pi

def archimedes_sequence(precision=1e-5):
    n = 2
    dn = math.sqrt(2)
    delta = 999
    while delta > precision:
        d2n = math.sqrt(2 - 2 * math.sqrt(1 - dn ** 2 / 4))
        if d2n < 0.0000001:
            break
        delta = abs(d2n * 2 * n - dn * n)
        dn = d2n
        n *= 2
    return dn * n

def monte_carlo(precision=1e-5):
    shots = 0
    hits = 0
    delta = 999
    while delta > precision:
        prev = hits / shots * 4 if shots else 0
        for _ in range(1000):
            x, y = random.random(), random.random()
            if x * x + y * y < 1:
                hits += 1
            shots += 1
        delta = abs(prev - hits / shots * 4)
    return hits / shots * 4


# 652. Find Duplicate Subtrees
# https://leetcode.com/problems/find-duplicate-subtrees/

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    # def findDuplicateSubtrees(self, root: Optional[TreeNode]) -> List[Optional[TreeNode]]:
    def dfs(self, root):
        if not root:
            return " "
        n = str(root.val) + "," + self.dfs(root.left) + "," + self.dfs(root.right)
        if n in self.s and n not in self.counted:
            self.counted[n] = 1
            self.ans.append(root)
            return n
        self.s.add(n)
        return n

    def findDuplicateSubtrees(
        self, root: Optional[TreeNode]
    ) -> List[Optional[TreeNode]]:
        self.s = set()
        self.ans = []
        self.counted = dict()
        self.dfs(root)
        return self.ans


import os, sys, stat
from pathlib import Path
from os.path import realpath
import json
import numpy as np
from osgeo import  osr, gdal, ogr
import matplotlib as mpl
import rtree
from pyearth.visual.scatter.scatter_plot_data import scatter_plot_data
from pyearth.toolbox.reader.text_reader_string import text_reader_string
from pyearth.visual.scatter.scatter_plot_multiple_data import scatter_plot_multiple_data
from pyflowline.external.pyearth.gis.gdal.gdal_functions  import meter_to_degree, degree_to_meter
from pyearth.visual.color.create_diverge_rgb_color_hex import create_diverge_rgb_color_hex
from pyhexwatershed.pyhexwatershed_read_model_configuration_file import pyhexwatershed_read_model_configuration_file
from pyflowline.formats.read_flowline import read_flowline_geojson
from pyflowline.mesh.dggrid.create_dggrid_mesh import dggrid_find_resolution_by_index
from pyflowline.external.pyearth.gis.gdal.gdal_functions import gdal_read_geotiff_file, reproject_coordinates
sPath_parent = str(Path(__file__).parents[2]) # data is located two dir's up
print(sPath_parent)
sPath_data = realpath( sPath_parent +  '/data/amazon' )
sWorkspace_input =  str(Path(sPath_data)  /  'input')
sWorkspace_output = '/compyfs/liao313/04model/pyhexwatershed/amazon'
aResolution_index = [10, 11, 12, 13]
nCase = len(aResolution_index)
aCase_index = aResolution_index
sDate='20230801'
sMesh_type = 'dggrid'
sFilename_configuration_in = realpath( sPath_parent +  '/examples/amazon/pyhexwatershed_amazon_dggrid.json' )

# we define a dictionnary with months that we'll use later
case_dict = dict()

for i in range(1, nCase +1):
    case_dict[i] = 'Case ' +  "{:0d}".format(i)

print(case_dict)

aDistance_niws =list()
aData_x=list()
aData_y  =list()
aLabel_legend =list()

#read the point location using gdal
pSrs = osr.SpatialReference()
pSrs.ImportFromEPSG(4326)    # WGS84 lat/lon
sFilename_confluence = '/qfs/people/liao313/data/hexwatershed/amazon/vector/amztrbmth.geojson'
pDriver = ogr.GetDriverByName('GeoJSON')
pDataSource = pDriver.Open(sFilename_confluence, 0)
pLayer = pDataSource.GetLayer()
nPoint = pLayer.GetFeatureCount()
aLongitude = np.full(nPoint, None, dtype=float)
aLatitude = np.full(nPoint, None, dtype=float)
for i in range(nPoint):
    pFeature = pLayer.GetFeature(i)
    pGeometry = pFeature.GetGeometryRef()
    #get x and y of the point
    dLongitude = pGeometry.GetX()
    dLatitude = pGeometry.GetY()
    aLongitude[i] = dLongitude
    aLatitude[i] = dLatitude

pDataSource = None

#the flow accumenation is in the same order as the point

sFilename_flow_accumulation = '/compyfs/liao313/00raw/hydrology/amazon/CD06_CAMREX_1086/data/amzfloacc.tif'
pRaster = gdal.Open(sFilename_flow_accumulation)
aFlow_accumulation_in, dPixelWidth, dOriginX, dOriginY, \
            nrow, ncolumn,dMissing_value, pGeotransform, pProjection,  pSpatialRef_target = gdal_read_geotiff_file(sFilename_flow_accumulation)

dResolution_degree_in= 0.005
dLatitude_mean = np.mean(aLatitude)
dResolution_flow_accumulation = degree_to_meter( dLatitude_mean ,dResolution_degree_in)

dX_left=dOriginX
dX_right = dOriginX + ncolumn * dPixelWidth
dY_top = dOriginY
dY_bot = dOriginY - nrow * dPixelWidth
aFlow_accumulation = np.full(nPoint, None, dtype=float)
for i in range(nPoint):
    x1 = aLongitude[i]
    y1 = aLatitude[i]
    dX_out,dY_out = reproject_coordinates(x1,y1, pSrs,pSpatialRef_target)
    dDummy1 = (dX_out - dX_left) / dPixelWidth
    lColumn_index = int(dDummy1)
    dDummy2 = (dY_top - dY_out) / dPixelWidth
    lRow_index = int(dDummy2)
    if lColumn_index >= ncolumn or lColumn_index < 0 \
        or lRow_index >= nrow or lRow_index < 0:
        #this pixel is out of bound
        continue
    else:
        dFlow_accumulation = aFlow_accumulation_in[lRow_index, lColumn_index]
        aFlow_accumulation[i] = dFlow_accumulation * dResolution_flow_accumulation * dResolution_flow_accumulation / 1.0E6

#read the model resolution
sDggrid_type = 'ISEA3H'
aDrainage_area = np.full(( nCase,nPoint), None, dtype=float)
for iCase in range(0, nCase ):
    #read
    iCase_index = aCase_index[iCase]
    iResolution_index = aResolution_index[iCase]
    dResolution = dggrid_find_resolution_by_index(sDggrid_type, iResolution_index)
    print(dResolution)

    oPyhexwatershed = pyhexwatershed_read_model_configuration_file(sFilename_configuration_in,
                    iCase_index_in=iCase_index,
                    sDate_in= sDate, sMesh_type_in= sMesh_type)

    #read the variable polygon
    sFilename_polygon = oPyhexwatershed.aBasin[0].sFilename_variable_polygon
    pDataSource = pDriver.Open(sFilename_polygon, 0)
    pLayer_base = pDataSource.GetLayer()
    nFeature_base = pLayer_base.GetFeatureCount()
    index_base = rtree.index.Index()

    for i in range(nFeature_base):
        lID = i
        pFeature_base = pLayer_base.GetFeature(i)
        pGeometry_base = pFeature_base.GetGeometryRef()
        left, right, bottom, top= pGeometry_base.GetEnvelope()
        pBound= (left, bottom, right, top)
        index_base.insert(lID, pBound)  #

    #find the confluence point
    for i in range(nPoint):
        #convert meter to degree
        dLatitude_mean=     aLatitude[i]
        dResolution_degree = meter_to_degree(dResolution, dLatitude_mean) * 2
        pBound= (aLongitude[i] - dResolution_degree,
                 aLatitude[i] - dResolution_degree,
                 aLongitude[i] + dResolution_degree,
                 aLatitude[i] + dResolution_degree)
        aIntersect = list(index_base.intersection(pBound))
        aDummy = list()
        for k in aIntersect:
            pFeature_base = pLayer_base.GetFeature(k)
            dDrainage_base = pFeature_base.GetField("drainage_area")
            aDummy.append(dDrainage_base/ 1.0E6)

        #find the closese to the observation
        dFlow_accumulation = aFlow_accumulation[i]
        #calculate the difference
        aDummy = np.asarray(aDummy)
        aDummy0 = np.abs(aDummy - dFlow_accumulation)
        #find th minimum and its index
        iIndex = np.argmin(aDummy0)
        aDrainage_area[iCase, i] = aDummy[iIndex]

    #close the file
    pDataSource = None

sFilename_out = sPath_parent + '/' + 'figures' + '/' + 'scatterplot_drainage_area_multiple.png'

#5 cases + 1 obs

aColor = np.full(nCase, None, dtype=object)
aMarker= np.full(nCase, None, dtype=object)
aSize = np.full(nCase, mpl.rcParams['lines.markersize'] ** 2 , dtype=object)

nmesh=nCase
aColor= create_diverge_rgb_color_hex(nmesh)
aMarker = [ '.','o','+','x','^']

aFlow_accumulation = np.array(aFlow_accumulation)
aData_x = list()
aData_y = list()

for i in range(nCase):
    iCase_index = aCase_index[i]
    aLabel_legend.append(   'ISEA3H Level ' +  "{:0d}".format(iCase_index)   )
    aDatax0 = list()
    aDatay0 = list()
    for j in range(nPoint):
        aDatax0.append(aFlow_accumulation[j])
        aDatay0.append(aDrainage_area[i,j])

    aDatax0 = np.asarray(aDatax0)
    aDatay0 = np.asarray(aDatay0)
    aData_x.append(aDatax0)
    aData_y.append(aDatay0)

scatter_plot_multiple_data(aData_x,
                      aData_y,
                      sFilename_out,
                      iFlag_scientific_notation_x_in=1,
                      iFlag_scientific_notation_y_in=1,
                      iSize_x_in = None,
                      iSize_y_in = None,
                      iDPI_in = None ,
                      iFlag_log_x_in = None,
                      iFlag_log_y_in = None,
                      dMin_x_in = 0,
                      dMax_x_in = np.max(aFlow_accumulation),
                      dMin_y_in = 0,
                      dMax_y_in = np.max(aFlow_accumulation),
                      dSpace_x_in = None,
                      dSpace_y_in = None,
                      sFormat_x_in =None,
                      sFormat_y_in =None,
                      sLabel_x_in ='LBA-ECO data, $km^{2}$',
                      sLabel_y_in = 'HexWatershed, $km^{2}$)' ,
                      aColor_in=aColor,
                      aMarker_in=aMarker,
                      aSize_in = aSize,
                      aLabel_legend_in = aLabel_legend,
                      sTitle_in = 'Drainage area')

print('finished')


from scrapymasters.common.ConfigFiles import ConfigFiles
from scrapymasters.common.MongoUtils import MongoUtils
from string import punctuation

class IndexerPipeline(object):
    def __init__(self):
        config = ConfigFiles.config()
        self.client = MongoUtils.create_client_from_config(config)
        self.db = self.client.scrape
        self.bulk = self.db.words.initialize_ordered_bulk_op()

    def process_item(self, article, spider):
        body = article['body']
        for word in body.split():
            word = word.strip(punctuation).lower()

            if len(word) != 0 and word.isalpha():
                url = article['url']
                word_query = { "word": word }
                url_to_insert = {"$addToSet": {"urls": url }}
                self.bulk.find(word_query).update(url_to_insert)
        return article

    def close_spider(self, spider):
        result = self.bulk.execute()
        print("Index write results:")
        print(result)
        self.client.close()


a=list(map(int,input().split()))
i=a[0]
j=a[1]

for ele in a:
    if ele>i :
        i=ele
    elif j<ele and j!=i:
        j=ele
print(i,j)


import numpy as np
from scipy.spatial import cKDTree
from scipy.sparse import coo_matrix, csr_matrix, csc_matrix, lil_matrix
from scipy.fftpack import dst, dct, next_fast_len
from multiprocessing import Pool
import math
from collections import defaultdict

# This gives the probability that two points placed in the unit square with
# uniform distributions has a squared distance smaller than s2.
# See e.g. https://people.kth.se/~johanph/habc.pdf on how to generalize this to non-square images.
def gCum(r):
	# return -4 * np.sqrt(s2) + np.pi + s2 if s2 < 1 else -2 + 4 * np.arcsin(1/np.sqrt(s2)) + 4*np.sqrt(s2-1) - np.pi - s2
	return np.pi*r**2-8*r**3/3+r**4/2 if r<1 else 1/3 - 2 * r**2 - np.pi*r**2 - r**4/2 + 4*np.sqrt(r**2 - 1) + 8/3*(r**2-1)**(3/2) + 4*r**2*np.arcsin(1/r)

def g(r):
	r2 = r*r
	return 2*r*(-4 * r + np.pi + r2 if r2 <= 1 else -2 + 4 * np.arcsin(1/r) + 4*np.sqrt(r2-1) - np.pi - r2)

def cellDiff(x1, x2, W, torus):
	d = x2 - x1
	if torus:
		d = np.remainder(d + W/2, W) - W/2
	return d

def getPairGrad(arg):
	pair, cells, trees, qs, W, z, torus, gradient = arg
	maxDist = 2*np.pi/qs[0]
	c1, c2 = [cells[t] for t in pair]
	# if pair[0]==pair[1]:
	# 	cellPairs = trees[pair[0]].query_pairs(maxDist, output_type = 'ndarray')
	# else:
	# 	neighbors = trees[pair[0]].query_ball_tree(trees[pair[1]], maxDist)
	# 	cellPairs = np.array([(i, j) for i in range(len(c1)) for j in neighbors[i]])

	if False and len(cellPairs) == 0:
		diff = np.zeros((0,2))
		if gradient:
			return qs*0, [ [ np.zeros((len(cells[pair[ci]]), len(qs)))  for di in range(2)] for ci in range(2)]
		else:
			return qs*0,
	else:
		# diff = cellDiff(c1[cellPairs[:,0]], c2[cellPairs[:,1]], W, torus)
		diff = cellDiff(c1[:, np.newaxis], c2[np.newaxis, :], W, torus).reshape(-1, 2)
	dists = np.linalg.norm(diff, axis=1)
	dists = dists[dists>1] #remove self-pairs

	if dists.shape[0] == 0:
		diff = np.zeros((0,2))
		if gradient:
			return qs*0, [ [ np.zeros((len(cells[pair[ci]]), len(qs)))  for di in range(2)] for ci in range(2)]
		else:
			return qs*0,

		# ddistdx[ti][di][cell, dist]
	# l2 =  4/qs[0]**2
	# dqrdx = [ [ddistdx[ci][di][:, np.newaxis] * qs[np.newaxis, :] for di in range(2)] for ci in range(2)]
	dq = qs[1]-qs[0]

	n = len(qs)

	batchSize = 10000 #batch this sum to not run out fo memory
	S = 0
	if True:
		edges = np.linspace(0, W*np.sqrt(2), 10*n)
		binCenters = (edges[:-1]+edges[1:])/2
		counts,_ = np.histogram(dists, edges)
		weights = np.array([(2*np.pi*(d/W))/g(d/W) for d in binCenters])
		normCounts = counts*weights
		uniform = np.diff(np.pi*edges**2)*c1.shape[0]*c2.shape[0]/W**2
		interp = 1/(1+np.exp(-(binCenters-0.5*W)/(0.1*W)))
		normCounts = normCounts*(1-interp) + interp*uniform
		normCounts[binCenters>W*1.2] = uniform[binCenters>W*1.2]
		# import matplotlib.pyplot as pl
		# pl.figure()
		# pl.plot(binCenters,counts)
		rRes = self.W/np.sqrt(len(self.cells[pair[0]])*len(self.cells[pair[1]]))

		S += np.sum( normCounts[:, np.newaxis]*np.sin( binCenters[:, np.newaxis] * qs[np.newaxis, :] )*np.exp(-binCenters[:, np.newaxis]**2/(2/dq**2)), axis=0)
	else:
		if torus:
			weights = np.ones(dists.shape)
		else:
			assert(gradient==False) #not handling derivative of this..
			weights = np.array([(2*np.pi*(d/W))/g(d/W) for d in dists])
			weights = np.diff(np.pi*edges**2)/np.diff(gCum(edges/W)*W*W)
		for i in range(1+dists.shape[0]//batchSize):
			S += np.sum( weights[i*batchSize:(i+1)*batchSize, np.newaxis]*np.sin( dists[i*batchSize:(i+1)*batchSize, np.newaxis] * qs[np.newaxis, :] )*np.exp(-dists[i*batchSize:(i+1)*batchSize, np.newaxis]**2/(2/dq**2)), axis=0)
	S *=  2 / qs / z
	# S = np.sum( weights[:, np.newaxis]*np.sin( dists[:, np.newaxis] * qs[np.newaxis, :] )*np.exp(-dists[:, np.newaxis]**2/(2*l2)) / qs[np.newaxis, :] / dists[:, np.newaxis], axis=0)
	if gradient:
		ddistdx = [ [csr_matrix(([-1,1][ci]*diff[:,di]/dists, (cellPairs[:, ci], np.arange(len(dists)) )), shape=(len(cells[pair[ci]]), len(dists))) for di in range(2)] for ci in range(2)]
		return S, [ [ ddistdx[ci][di].dot(np.cos( dists[:, np.newaxis] * qs[np.newaxis, :] )*np.exp(-dists[:, np.newaxis]**2/(2*l2)) / dists[:, np.newaxis]
                    -  np.sin( dists[:, np.newaxis] * qs[np.newaxis, :] )*np.exp(-dists[:, np.newaxis]**2/(2*l2)) / qs[np.newaxis, :]/dists[:, np.newaxis]**2
		    - np.sin( dists[:, np.newaxis] * qs[np.newaxis, :] )*np.exp(-dists[:, np.newaxis]**2/(2*l2)) / qs[np.newaxis, :] /l2)  for di in range(2)] for ci in range(2)]
	else:
		return S,

# We define fourier transfom as:
#	F(q) = int d^3r f(r) exp(-iq dot r)
#	f(r) = int d^3q F(q) exp(iq dot r) / (2pi)^3
# The functions are rotationally symmetric so the angular part can be done. The remaining radial part becomes a sine transform
# that is implemented through a discreticed integral in the two functions below. They are exact inverses of each other

# sum i f(i dqr ) * dq * q * sin(dr * dq * i * j + ..) / (r 2 pi^2)
def radialFourierTransformQToR(qs, fs, deWindow = False, rResolution=0):
	n = len(qs)
	dq = qs[1]-qs[0]
	rs = (0.5+np.arange(n))*np.pi/n/dq
	dr = rs[1]-rs[0]
	# return rs, dst(fs * qs * np.exp(-0*qs**2*rResolution**2), type=4)*(np.exp(rs*rs*dq**2/2) if deWindow else 1) * dq  / (4*np.pi**2)
	return rs, dst(fs * qs * np.exp(-0*2*qs**2/(qs[-1]**2)), type=4)*(np.exp(0*rs*rs*dq**2/2) if deWindow else 1) * dq / rs / (4*np.pi**2)

# sum i f(i dr ) * dr * r * sin(dr * dq * i * j + ..) 4 pi / q
def radialFourierTransformRToQ(rs, fs, deWindow = False):
	n = len(rs)
	dr = rs[1]-rs[0]
	qs = (0.5+np.arange(n))*np.pi/n/dr
	dq = qs[1]-qs[0]
	return qs, dst(fs * rs * np.exp(-0*rs*rs*dq**2/2), type=4) / qs * np.exp(0*2*qs**2/(qs[-1]**2)) * dr * (2*np.pi)
	#dst = 2*sin(..)
	# 4 pi / (2 pi)^3 = 1/(2 pi^2)
	# n = len(rs)
	# qs = np.arange(1,n+1)*np.pi/(n+1)/rs[0]
	# return qs, dst(fs * rs, type=1) / qs

def getRadialConvolutionMatrix(dr, n, fs):
	inds = np.arange(n, dtype = np.int32)
	rs = (0.5 + inds) * dr
	indsSum = inds[:, np.newaxis] + inds[np.newaxis, :]
	indsDiff = np.abs(inds[:, np.newaxis] - inds[np.newaxis, :])
	frsCum = np.cumsum(np.concatenate(([0], fs*rs, np.zeros(n))))
	return 2 * np.pi * dr**2 * rs[np.newaxis, :] * (frsCum[indsSum + 1] - frsCum[indsDiff]) / rs[:, np.newaxis]

class CellDistribution():
	def __init__(self, cells, W, z = 1, torus = False):
		self.cells = {t:np.array(cells[t]) for t in cells}
		self.W, self.z = W, z
		self.torus = torus
		self.trees = {t:cKDTree(cells[t], boxsize = (W, W) if torus else None) for t in cells}

	def cellDiff(self, x1, x2):
		d = x2 - x1
		if self.torus:
			d = np.remainder(d + self.W/2, self.W) - self.W/2
		return d

	#This returns g for all cell type pairs, the results are regularized where data is missing. g(0)=0 and g(inf)=1 are assumed.
	#Perhaps input expected cell sizes to this for better guesses?
	def getG(self, dr, n):
		cellCounts = self.getCounts()
		rhos = self.getRhos()

		edges = np.arange(n+1)*dr
		rs = (0.5+np.arange(n))*dr

		# we assume grid cells that are not direct neighbors are independent. This is a bit optimistic.
		# We also average over the direct neighbors, and they are not completely indep but we do not count this as reducing variance, this is a bit conservative.
		gridResolution = 3 #assume this is fixed
		gridEdges = np.linspace(0, self.W, gridResolution+1)
		gridCellW = gridEdges[1] - gridEdges[0]
		margin = 0.2
		assert(gridCellW*np.sqrt(2)*(1-margin) >= edges[-1])

		assert(gridResolution%2==1)
		M  = [np.zeros((n*len(self.cells)*( len(self.cells) + 1 )//2, groupSize)) for groupSize in [(gridResolution**2+1)//2, (gridResolution**2-1)//2]] #this depends on gridResolution being odd
		groupI = 2*[0]
		for i in range(gridResolution**2):
			group = (i//gridResolution + i%gridResolution) % 2
			(x0, x1), (y0, y1) = gridEdges[i//gridResolution:i//gridResolution+2], gridEdges[i%gridResolution:i%gridResolution+2]
			j=0
			for t1 in self.cells:
				for t2 in self.cells:
					if t1 <= t2:
						c1, c2 = [ps[(ps[:,0] >= x0) & (ps[:,0] < x1) & (ps[:,1] >= y0) & (ps[:,1] < y1)] for ps in [self.cells[t1], self.cells[t2]]]
						diff = self.cellDiff(c1[:, np.newaxis], c2[np.newaxis, :]).reshape(-1, 2)
						dists = np.linalg.norm(diff, axis=1)
						counts, _ = np.histogram(dists[dists>1], edges)
						M[group][j:j+n, groupI[group]] = counts / (np.diff([gCum(e/gridCellW)*gridCellW*gridCellW for e in edges]) * len(c1) * len(c2) / gridCellW**2)
						j += n
			groupI[group] += 1
		averageByGroup = [np.mean(M[g], axis=1) for g in range(2)]
		covByGroup = [np.cov(M[g]) / groupI[g] for g in range(2)] #estimate of covariance of components in g in a single grid cell
		#the end result is the average over many different grid cells and we assume non-neighboring ones are indep. covariance is simply summed assuming iid vars. Average contributes 1/

		#in the end we just average the two groups. Since they are not independent, we do not reduce the covariance
		average, cov = [np.average(v, axis=0, weights=groupI) for v in [averageByGroup, covByGroup]]

		vals, vecs = np.linalg.eigh(cov) #vecs[:, i] is the i:th vector

		# np.ei

		import matplotlib.pyplot as pl
		from matplotlib.colors import hsv_to_rgb
		pl.figure()

		# for i in range(gridResolution):
			# for j in range(gridResolution):
		j=0
		for t1 in self.cells:
			for t2 in self.cells:
				if t1 <= t2:
					col = hsv_to_rgb((j/(n*(len(self.cells)+1)*len(self.cells)/2),1,1))
					for g in range(2):
						pl.errorbar(rs, averageByGroup[g][j:j+n], yerr=np.sqrt(np.diag(covByGroup[g])[j:j+n]), color = col, linestyle = ['-','--'][g])
					j+=n

		pl.figure()
		j=0
		for t1 in self.cells:
			for t2 in self.cells:
				if t1 <= t2:
					col = hsv_to_rgb((j/(n*(len(self.cells)+1)*len(self.cells)/2),1,1))
					pl.errorbar(rs, average[j:j+n], yerr=np.sqrt(np.diag(cov)[j:j+n]), color = col)
					j+=n

		pl.show()
		exit(0)

		#Distances longer than this are too influenced by boundary effects in crop case
		#and finite size effects in toroidal case. We set such values to 1
		maxMeasR = self.W*0.8
		transitionWidth = self.W*0.10
		nTransitionsCutoff = 3

		g = {}
		for t1 in self.cells:
			for t2 in self.cells:
				if t1 <= t2:
					c1 = self.cells[t1]
					c2 = self.cells[t2]
					diff = self.cellDiff(c1[:, np.newaxis], c2[np.newaxis, :]).reshape(-1, 2)
					dists = np.linalg.norm(diff, axis=1)
					dists = dists[dists>0]
					counts,_ = np.histogram(dists, edges)
					gMeas = counts / (np.diff([gCum(e/self.W)*self.W*self.W for e in edges]) * self.z * rhos[t1] * cellCounts[t2])
					interp = 1/(1+np.exp(-(rs-(maxMeasR - nTransitionsCutoff*transitionWidth))/transitionWidth))
					interp[rs>maxMeasR] = 1

					gInterp = gMeas*(1 - interp) + 1*interp

					rRes = 2*self.W/np.sqrt(cellCounts[t1]*cellCounts[t2])
					kernelN = math.ceil(4*rRes / dr)
					kernel = np.exp(-np.linspace(-kernelN*dr, kernelN*dr, 2*kernelN+1)**2 / (2*rRes**2))
					kernel /= np.sum(kernel)
					g[(t1,t2)] = gInterp
					# g[(t1,t2)] = np.convolve(np.concatenate([np.zeros(kernelN), gInterp, np.ones(kernelN)]), kernel, mode='valid')
		return g

	def getCounts(self):
		return {t:self.cells[t].shape[0] for t in self.cells}

	def getRhos(self):
		return {t:self.cells[t].shape[0]/self.W**2/self.z for t in self.cells}

	def getCorrs(self, edges):
		if self.torus:
			assert( edges[-1] <= self.W/2 ) #longer than this and we need to consider multiple paths
			genAreas = np.pi*edges**2
		else:
			assert( edges[-1] <= self.W*np.sqrt(2) )
			genAreas = [gCum(e/self.W) for e in edges]
		# print(genAreas)
		uniformProbabilitiesPerBin = np.diff(genAreas) # / self.W**2
		corrs, errs = {}, {}
		for t1 in self.trees:
			for t2 in self.trees:
				if t1 > t2: continue
				c1, c2 = [self.cells[t] for t in [t1,t2]]
				if t1==t2:
					if len(c1) < 2: continue
					pairs = self.trees[t1].query_pairs(edges[-1], output_type = 'ndarray')
					if len(pairs):
						norm = 2 / uniformProbabilitiesPerBin / len(c1) / (len(c1)-1)
					else: norm = 1
				else:
					neighbors = self.trees[t1].query_ball_tree(self.trees[t2], edges[-1])
					pairs = np.array([(i, j) for i in range(len(c1)) for j in neighbors[i]])
					if len(pairs):
						norm = 1 / uniformProbabilitiesPerBin / len(c1) / len(c2)
					else: norm = 1
				if len(pairs) == 0:
					dists = []
				else:
					dists = cellDiff(c1[pairs[:,0]], c2[pairs[:,1]], self.W, self.torus)
				hist = np.histogram(dists, edges)[0]
				corrs[(t1,t2)] =  hist * norm
				errs[(t1,t2)] = np.sqrt(hist + 1) * norm
		return corrs, errs

	# This returns structure factor - 1, we do not count cells distances to themselves.
	def getStructureFactors(self, qs, gradient=False, nThreads = 1):
		print("Calculating structure factor...")
		rs = self.getRs(qs)
		_, gs = self.getG(rs[1]-rs[0], len(qs))

		# pairs = [(t1, t2) for t1 in self.trees for t2 in self.trees if t1<=t2 and not (t1==t2 and len(self.cells[t1])==0)]
		# if nThreads>1:
		# 	with Pool(nThreads) as pool:
		# 		res = list(zip(*pool.map(getPairGrad, [(p, self.cells, self.trees, qs, self.W, self.z, self.torus, gradient) for p in pairs])))
		# else:
		# 	res = list(zip(*[getPairGrad((p, self.cells, self.trees, qs, self.W, self.z, self.torus, gradient)) for p in pairs]))

		# if gradient:
		# 	S = dict(zip(pairs, res[0]))
		# 	dSdx = dict(zip(pairs, res[1]))
		# 	return S, dSdx
		# else:
		# 	return [dict(zip(pairs, res[0]))]

	def getQs(self, resolution):
		maxr = self.W * np.sqrt(2) #*2, <- works fine with larger values too, though its slower. Perhaps good for less aliasing?
		n = next_fast_len(int(2*maxr/resolution+1))
		return (0.5+np.arange(n)) * np.pi / maxr

	def getRs(self, qs):
		dq = qs[1]-qs[0]
		n = len(qs)
		return (0.5+np.arange(n))*np.pi/n/dq

	def getH(self, qs, S=None):
		rs = self.getRs(qs)
		_, gs = self.getG(rs[1]-rs[0], len(qs))
		return {p:radialFourierTransformRToQ(rs, gs[p]-1)[1] for p in gs}

		# if S==None:
		# 	S, = self.getStructureFactors(qs)
		# counts = self.getCounts()
		# rhos = self.getRhos()
		# Sn = {p:S[p]/(counts[p[0]]*rhos[p[1]]) for p in S}
		# rs = self.getRs(qs)
		# dq = qs[1] - qs[0]
		# window = np.exp(-rs**2/(2/dq**2))
		# window =  np.ones(len(rs))
		# _,deltaFun = radialFourierTransformRToQ(rs, window)

		# # import matplotlib.pyplot as pl
		# # pl.figure()
		# # for p in Sn:
		# # 	pl.plot(rs, Sn[p], label=p)
		# # pl.plot(rs, deltaFun, label="delta")
		# # pl.legend()

		# # pl.figure()
		# # for p in Sn:
		# # 	pl.plot(rs, radialFourierTransformQToR(qs, Sn[p])[1], label=p)
		# # pl.plot(rs, radialFourierTransformQToR(qs, deltaFun)[1], label="delta")
		# # pl.legend()
		# # pl.show()
		# # exit(0)

		# return {p:Sn[p]-deltaFun for p in S}

	def getRRes(self, pair):
		return self.W/np.sqrt(len(self.cells[pair[0]])*len(self.cells[pair[1]]))

	def getRadialDistributionFunction(self, resolution):
		qs = self.getQs(resolution)

		print("Preparing matrices...")
		types = list(self.cells.keys())
		rhos = self.getRhos()
		rhoVec = np.array([rhos[t] for t in types])

		# S, = self.getStructureFactors(qs)
		H = self.getH(qs)
		print(H)
		Hmat = np.array([[[H[(min(t1,t2), max(t1, t2))][i] for t2 in types] for t1 in types] for i in range(len(qs))])

		print("Solving...")
		# h = d + rho h d
		DMat = np.linalg.solve( np.eye(len(types))[np.newaxis,:,:] + rhoVec[np.newaxis, np.newaxis, :]*Hmat, Hmat)
		# DMat = np.linalg.solve( np.eye(len(types))[np.newaxis,:,:] , Smat)

		counts = self.getCounts()
		rhos = self.getRhos()
		# Sn = {p:S[p]/(counts[p[0]]*rhos[p[1]]) for p in S}
		h = {p:radialFourierTransformQToR(qs, H[p]) for p in H}
		C = {p:radialFourierTransformQToR(qs, DMat[:, types.index(p[0]), types.index(p[1])]) for p in H} #no dewindow, expect to go to 0 anyways..
		# return g, C, rhoVec[np.newaxis, np.newaxis, :]*Hmat,  DMat
		return h, C, rhoVec[np.newaxis, np.newaxis, :]*Hmat

	def __getBasisXInd(self, nt, nb):
		types = list(self.cells.keys())
		return [(t1,t2, bi) for t1 in range(nt) for t2 in range(nt) for bi in range(nb) if types[t1]<=types[t2]]

	def getCFromBasis(self, x, basis, dr, n):
		xInds =  self.__getBasisXInd(len(self.cells), len(basis))
		c = defaultdict(lambda: np.zeros((n)))
		rs = (0.5+np.arange(n)) * dr
		types = list(self.cells.keys())
		for i in range(len(xInds)):
			c[(types[xInds[i][0]], types[xInds[i][1]])] += x[i]*basis[xInds[i][2]]
		return c

	#
	def getC(self, dr, n, basis = None, longRangeReg = 0, highFreqReg = 0):
		gs = self.getG(dr, n)

		dq=np.pi/n/dr
		qs = (0.5+np.arange(n)) * dq
		rs = (0.5+np.arange(n)) * dr

		types = list(self.cells.keys())
		nt = len(types)
		rhos = self.getRhos()
		rhoVec = np.array([rhos[t] for t in types])

		if longRangeReg>0 or highFreqReg>0:
			h = np.zeros((n, len(types), len(types)))
			for i in range(len(types)):
				for j in range(len(types)):
					h[:,i,j] = gs[(min(types[i], types[j]), max(types[i], types[j]))] - 1

			xInd_To_CT1T2Q = [(t1, t2, qi) for t1 in range(nt) for t2 in range(nt) for qi in range(n) if types[t1]<=types[t2]]
			CT1T2Q_To_xInd = {**{xInd_To_CT1T2Q[i]:i for i in range(len(xInd_To_CT1T2Q))}, **{(xInd_To_CT1T2Q[i][1], xInd_To_CT1T2Q[i][0], xInd_To_CT1T2Q[i][2]):i for i in range(len(xInd_To_CT1T2Q))}}

			nComps = nt*(nt+1)//2
			nEqs = nt**2*n + nComps*(n-1) + nComps*n
			A = np.zeros(( nEqs, nComps*n))
			b = np.zeros(( nEqs,))
			eqInd = 0
			for t2Eq in range(nt):
				for t1Eq in range(nt):
					for t1C in range(nt):
						xInd = CT1T2Q_To_xInd[(t1C, t2Eq, 0)]
						A[eqInd:eqInd+n, xInd:xInd+n] += getRadialConvolutionMatrix(dr, n, rhoVec[t1C]*h[:, t1Eq, t1C])
						if t1Eq==t1C:
							A[eqInd:eqInd+n, xInd:xInd+n] += np.eye(n)
						b[eqInd:eqInd+n] = h[:, t1Eq, t2Eq]
					eqInd += n

			for t2C in range(nt):
				for t1C in range(t2C+1):
					xInd = CT1T2Q_To_xInd[(t1C, t2C, 0)]
					A[eqInd:eqInd+n-1, xInd:xInd+n] = 0*highFreqReg / np.sqrt(dr) * (np.diag(np.ones(n)) - np.diag(np.ones(n-1), 1))[:-1, :] * rs[1:][:, np.newaxis]/20
					eqInd += n-1

			for t2C in range(nt):
				for t1C in range(t2C+1):
					xInd = CT1T2Q_To_xInd[(t1C, t2C, 0)]
					A[eqInd:eqInd+n, xInd:xInd+n] = 0*longRangeReg * np.sqrt(dr) * np.eye(n) * rs**2
					eqInd += n

			assert(eqInd == A.shape[0])

			# import matplotlib.pyplot as pl
			# pl.imshow(A==0)
			# pl.show()
			# exit(0)
			print("Solving c..")
			x, res, rank, _ = np.linalg.lstsq(A, b, rcond=None)
			print(res, rank, A.shape, nComps*n)
			print("done.")
			CMat = np.zeros((n, nt, nt))
			for i in range(len(xInd_To_CT1T2Q)):
				cInds = xInd_To_CT1T2Q[i]
				CMat[cInds[2], cInds[0], cInds[1]] = x[i]
				CMat[cInds[2], cInds[1], cInds[0]] = x[i]
			c = {(t1,t2):CMat[:, types.index(t1), types.index(t2)] for t1 in types for t2 in types if t1<=t2}
			return c
		else:
			H = np.zeros((n, len(types), len(types)))
			for i in range(len(types)):
				for j in range(len(types)):
					g = gs[(min(types[i], types[j]), max(types[i], types[j]))]
					qs, H[:,i,j] = radialFourierTransformRToQ(rs,  (g - 1)*np.exp(-0*rs**2/(2*200**2)))

			lhs = np.eye(len(types))[np.newaxis,:,:] + rhoVec[np.newaxis, np.newaxis, :]*H
			rhs = H
			if basis:
				A = []
				b = []
				Basis = [radialFourierTransformRToQ(rs,  b)[1] for b in basis]

				nt = len(types)
				xInds =  self.__getBasisXInd(nt, len(basis))
				# eqInd = [(t1,t2, ri) for t1 in range(nt) for t2 in range(nt) for ri in range(n) if t1<=t2]
				nEqs = nt**2*n #nt*(nt+1)//2*n
				A = np.zeros(( nEqs, len(xInds)))
				b = np.zeros((nEqs,))
				weightsByQ = np.ones(qs.shape)
				# weightsByQ = 1/qs
				# weightsByQ = qs**2
				for xi in range(len(xInds)):
					xc = np.zeros((n, nt, nt))
					xc[:, xInds[xi][0], xInds[xi][1]] += Basis[xInds[xi][2]]
					A[:, xi] = (np.einsum("ijk,ikl->ijl",lhs, xc)*weightsByQ[:,np.newaxis,np.newaxis]).flatten()
				b = (rhs*weightsByQ[:,np.newaxis,np.newaxis]).flatten()
				x = np.linalg.lstsq(A, b, rcond=None)[0]
				return x
			else:
				CMat = np.linalg.solve( lhs, rhs)

		c = {(t1,t2):radialFourierTransformQToR(qs, CMat[:, types.index(t1), types.index(t2)])[1] for t1 in types for t2 in types if t1<=t2}
		# c = {(t1,t2):CMat[:, types.index(t1), types.index(t2)] for t1 in types for t2 in types if t1<=t2}
		return c

	def getGFromC(self, c, dr, n, matrixConv = True):
		if matrixConv:
			dq=np.pi/n/dr
			qs = (0.5+np.arange(n)) * dq
			rs = (0.5+np.arange(n)) * dr

			types = list(self.cells.keys())
			nt = len(types)
			rhos = self.getRhos()
			rhoVec = np.array([rhos[t] for t in types])

			xInd_To_CT1T2Q = [(t1, t2, qi) for t1 in range(nt) for t2 in range(nt) for qi in range(n) if types[t1]<=types[t2]]
			CT1T2Q_To_xInd = {**{xInd_To_CT1T2Q[i]:i for i in range(len(xInd_To_CT1T2Q))}, **{(xInd_To_CT1T2Q[i][1], xInd_To_CT1T2Q[i][0], xInd_To_CT1T2Q[i][2]):i for i in range(len(xInd_To_CT1T2Q))}}

			nComps = nt*(nt+1)//2
			nEqs = nt**2*n
			A = np.zeros(( nEqs, nComps*n))
			b = np.zeros(( nEqs,))
			eqInd = 0
			for t2Eq in range(nt):
				for t1Eq in range(nt):
					for t1C in range(nt):
						xInd = CT1T2Q_To_xInd[(t1C, t2Eq, 0)]
						A[eqInd:eqInd+n, xInd:xInd+n] = - getRadialConvolutionMatrix(dr, n, rhoVec[t1C]*c[(min(types[t1Eq], types[t1C]), max(types[t1Eq], types[t1C]))])
						if t1Eq==t1C:
							A[eqInd:eqInd+n, xInd:xInd+n] += np.eye(n)
						b[eqInd:eqInd+n] = c[(min(types[t1Eq], types[t2Eq]), max(types[t1Eq], types[t2Eq]))]
					eqInd += n

			assert(eqInd == A.shape[0])

			print("Solving h..")
			x, res, _, _ = np.linalg.lstsq(A, b, rcond=None)
			print(res)
			print("done.")

			HMat = np.zeros((n, nt, nt))
			for i in range(len(xInd_To_CT1T2Q)):
				cInds = xInd_To_CT1T2Q[i]
				HMat[cInds[2], cInds[0], cInds[1]] = x[i]
				HMat[cInds[2], cInds[1], cInds[0]] = x[i]
			g = {(t1,t2):(rs, 1 + HMat[:, types.index(t1), types.index(t2)]) for t1 in types for t2 in types if t1<=t2}
			return g
		else:
			dq=np.pi/n/dr
			qs = (0.5+np.arange(n)) * dq
			rs = (0.5+np.arange(n)) * dr

			types = list(self.cells.keys())
			C = np.zeros((rs.shape[0], len(types), len(types)))
			rhos = self.getRhos()
			rhoVec = np.array([rhos[t] for t in types])

			for i in range(len(types)):
				for j in range(len(types)):
					C[:,i,j] = radialFourierTransformRToQ(rs, c[(min(types[i], types[j]), max(types[i], types[j]))])[1]

			HMat = np.linalg.solve( np.eye(len(types))[np.newaxis,:,:] - rhoVec[np.newaxis, np.newaxis, :]*C, C)
			# print("traspose:")
			# print(HMat[0,:,:]-HMat[0,:,:].T)
			# print(HMat[0,:,:]+HMat[0,:,:].T)

			g = {(t1,t2):(rs,1+radialFourierTransformQToR(qs, HMat[:, types.index(t1), types.index(t2)])[1]) for t1 in types for t2 in types if t1<=t2}
		return g

	def getHardSphereG(self, sizes, resolution):
		qs = self.getQs(resolution)
		rs = self.getRs(qs)
		types = list(self.cells.keys())
		# c = np.zeros((rs.shape[0], len(types), len(types)))
		# C = np.zeros((rs.shape[0], len(types), len(types)))
		# rhos = self.getRhos()
		# rhoVec = np.array([rhos[t] for t in types])
		c={}
		for t1 in types:
			for t2 in types:
				if t1<=t2:
					c[(t1,t2)] = - 2*np.exp(-rs**2/(2*(sizes[t1]+sizes[t2])**2))
		# c[:, 0, 0] += 0.048*np.exp(-rs**2/(2*(30)**2))

		return self.getGFromC(c, rs[1]-rs[0], len(rs))

	def getSLoss(self, S0, qs, gradient=False):
		res = self.getStructureFactors(qs, gradient=gradient)
		S = res[0]
		L = sum([np.sum((S[(t1,t2)] - S0[(t1,t2)])**2) for t1 in self.cells for t2 in self.cells if t1<=t2])

		if gradient:
			dSdx = res[1]
			dLdx = {k:np.zeros((2,len(self.cells[k]))) for k in self.cells}
			for t1 in self.cells:
				for t2 in self.cells:
					if t1>t2:
						continue
					for ci in range(2):
						dLdx[[t1, t2][ci]] += 2*np.tensordot(dSdx[(t1,t2)][ci], S[(t1,t2)]-S0[(t1,t2)], axes=((2,),(0,)))
			return L, dLdx
		else:
			return L,

def generateCellDistribution(counts, S0, W, qs, maxIterations=100, start = None, callback=None, L0=None, startStep=1):

	cd = CellDistribution({c:np.random.rand(counts[c], 2)*W for c in counts} if start==None else start, W, torus=True)

	lr = 0.000001
	L=1000
	i=0
	def step(ps, delta, f):
		return {t: np.zeros(0,2) if len(ps[t])==0 else (np.remainder(ps[t] + f*delta[t], W)) for t in ps}

	previousStep = startStep
	while L>1 and i<maxIterations:
		L, dLdx = cd.getSLoss(S0, qs, gradient=True)
		if L0==None: L0=L
		print(f'L/L0: {L/L0*100:.1f}%')

		norm = np.sqrt(np.mean([np.mean(dLdx[t]**2) for t in cd.cells]))
		dLdxNorm = {t:np.transpose(dLdx[t]) / norm for t in cd.cells}

		steps = np.exp(np.linspace(-1., 0.5, 4))*previousStep
		Ls = [CellDistribution(step(cd.cells, dLdxNorm, -s),  W, torus=True).getSLoss(S0, qs)[0] for s in steps]
		# print(Ls)
		bestStep = steps[np.argmin(Ls)]
		print(f"best step: {bestStep:.2g} um")
		previousStep = bestStep

		cd = CellDistribution(step(cd.cells, dLdxNorm, -bestStep),  W, torus=True)
		if callback:
			callback(cd)
		i+=1
	return cd, previousStep

if __name__ == "__main__":
	print("Testing radial Fourier transforms..")
	qs = CellDistribution({}, 100).getQs(10)
	fs = np.random.rand(len(qs))
	rs, gs = radialFourierTransformQToR(qs, fs, deWindow = True)
	qs2, f2s = radialFourierTransformRToQ(rs, gs, deWindow = True)
	print(qs)
	print(qs2)
	print(fs)
	print(f2s)

	print("Testing convolution..")
	fs = np.zeros(len(qs))
	fs[len(fs)//2] = 1
	fs/=rs**2*4*np.pi
	sigma = 20
	kern = np.exp(-rs**2/(2*sigma**2))/(2*np.pi*sigma**2)**(3/2)

	_, kernTwice = radialFourierTransformQToR( qs, radialFourierTransformRToQ(rs, kern)[1] * radialFourierTransformRToQ(rs, kern)[1] )
	print("kernTwice:")
	print(kernTwice)
	print("kernTwice expected:")
	kernTwiceExpected = np.exp(-rs**2/(2*2*sigma**2))/(2*np.pi*2*sigma**2)**(3/2)
	print(kernTwiceExpected)

	_, smoothed = radialFourierTransformQToR( qs, radialFourierTransformRToQ(rs, fs)[1] * radialFourierTransformRToQ(rs, kern)[1] )
	print("smoothed:")
	print(smoothed)
	print(np.sum(smoothed*rs**2*4*np.pi))

	print("matrix mul convolution:")
	n = len(rs)
	dr = rs[1] - rs[0]
	print(getRadialConvolutionMatrix(dr, n, kern).dot(fs))
	print("... in opposite order:")
	print(getRadialConvolutionMatrix(dr, n, fs).dot(kern))

	exit(0)

	W = 300
	n = 100
	ps0 = {f"type{k}":np.random.rand(n, 2)*W for k in range(4)}

	ps = {f"type{k}":np.random.rand(n, 2)*W for k in range(4)}

	eps = 1e-6
	qs = np.linspace(0.1,1, 10)

	cd0 = CellDistribution(ps0, W, torus=True)
	S0, = cd0.getStructureFactors(qs)

	# generateCellDistribution(cd0.getCounts(), S0, W)

	testFiniteDifference = True
	if testFiniteDifference:
		cd = CellDistribution(ps, W, torus=True)
		L, dLdx = cd.getSLoss(S0, qs, gradient=True)

		for k in ps:
			for di in range(2):
					for i in range(n):
						shiftedPs = np.array(ps[k])
						shiftedPs[i, di] += eps
						finiteDiff = (CellDistribution({ki:shiftedPs if ki==k else ps[ki] for ki in ps}, W, torus=True).getSLoss(S0, qs)[0] - L) / eps
						print(f"finite diff: {finiteDiff}")
						print(f"analytic: {dLdx[k][di,i]}")


import abc
import json
import logging
import math
from copy import deepcopy

# TODO: Merge code from jqGrid and this together?
from library.pandas_utils import df_nan_to_none

class DataFrameJqGrid:
    fields = []
    allow_empty = True

    pager_id = '#pager'
    url = None
    caption = None
    colmodel_overrides = {}

    @abc.abstractmethod
    def get_dataframe(self):
        pass

    def __init__(self):
        self.extra_config = {}
        self._overrides = deepcopy(self.colmodel_overrides)

    def get_override(self, field_name):
        return self._overrides.get(field_name, {})

    def get_items(self, request):
        df = self.get_dataframe()
        df = df_nan_to_none(df)  # JSON can't handle NaN
        df = self.sort_dataframe(request, df)
        (df_slice, page_number, num_pages, num_records) = self.paginate_df(request, df)
        items = self.get_dataframe_rows(df_slice)
        return page_number, num_pages, items, num_records

    def sort_dataframe(self, request, df):
        sidx = request.GET.get('sidx')
        if sidx is not None:
            ascending = not request.GET.get('sord') == 'desc'
            if sidx == 'ID':
                df = df.sort_index(ascending=ascending)
            elif sidx in df.columns:
                df = df.sort_values(sidx, ascending=ascending)
            else:
                logging.error("Request: %s asked for grid sort index (sidx) of '%s' which is not in DF!", request.GET, sidx)
        return df

    def get_paginate_by(self, request):
        rows = request.GET.get('rows', self.get_config(False)['rowNum'])
        try:
            paginate_by = int(rows)
        except ValueError:
            paginate_by = 10
        return paginate_by

    def paginate_df(self, request, df):
        paginate_by = self.get_paginate_by(request)
        if not paginate_by:
            return df, None, None, None

        page_number = int(request.GET.get('page', 1))
        num_records = len(df)
        num_pages = int(math.ceil(num_records / paginate_by))

        start = (page_number - 1) * paginate_by
        end = start + paginate_by + 1
        df_slice = df[start:end]
        return df_slice, page_number, num_pages, num_records

    def get_dataframe_rows(self, df):
        items = []
        for i, row in df.iterrows():
            data = dict(iter(row.items()))
            data['ID'] = i
            items.append(data)
        return items

    def get_json(self, request):
        page_number, num_pages, items, num_records = self.get_items(request)
        data = {
            'page': int(page_number),
            'total': int(num_pages),
            'rows': items,
            'records': int(num_records),
        }
        return json.dumps(data)

    def get_default_config(self):
        config = {
            'datatype': 'json',
            'autowidth': True,
            'forcefit': True,
            'shrinkToFit': True,
            'jsonReader': {'repeatitems': False},
            'rowNum': 10,
            'rowList': [10, 15, 20, 25, 50],
            'sortname': 'ID',
            'viewrecords': True,
            'sortorder': "asc",
            'pager': self.pager_id,
            'altRows': True,
            'gridview': True,
            'height': 'auto',
        }
        return config

    def get_url(self):
        return str(self.url)

    def get_caption(self):
        return self.caption or ''

    def get_config(self, as_json=True):
        config = self.get_default_config()
        config.update(self.extra_config)
        config.update({'url': self.get_url(),
                       'caption': self.get_caption(),
                       'colModel': self.get_colmodels()})
        if as_json:
            config = json.dumps(config)
        return config

    def add_overrides(self, colmodel, column_name):
        override = self.get_override(column_name)
        if override:
            colmodel.update(override)
        return colmodel

    def get_colmodels(self, *args, **kwargs):
        colmodels = []
        df = self.get_dataframe()
        index_colmodel = self.get_index()
        self.add_overrides(index_colmodel, index_colmodel["index"])
        colmodels.append(index_colmodel)
        for column_name in df.columns:
            column = df[column_name]
            colmodel = self.column_to_colmodel(column, column_name)
            self.add_overrides(colmodel, column_name)
            colmodels.append(colmodel)
        return colmodels

    @staticmethod
    def get_index():
        return {
            'name': 'ID',
            'index': 'ID',
            'label': 'ID',
            'editable': False
        }

    @staticmethod
    def column_to_colmodel(column, column_name):
        colmodel = {
            'name': column_name,
            'index': column_name,
            'label': column_name,
            # TODO: dtype?
            'editable': True
        }
        return colmodel


from ._gene_bank import GeneBank
from ._gene import Gene
from ._chromosome import Chromosome
from .Mutator._add_mutator import AddMutator
from .Mutator._delete_mutator import DeleteMutator
from .Mutator._change_mutator import ChangeMutator
import networkx as nx
import numpy as np
import logging
import time
import copy
import itertools

logger = logging.getLogger(__name__)

class GACorrelationQsubsetSelector:
    """
    MostCorrelation Constructor for the circuit
    """

    def __init__(self, graph: nx.Graph, initial_gene_size=4, max_chromosome_size=80, verbose=False):
        self._graph = graph
        # add identical block to gene bank
        self._gene_bank = list(self._build_gene_bank())
        self._chromosomes = self._init_chromosomes(max_chromosome_size, initial_gene_size)
        self._mutators = self._init_mutators()
        self.verbose = verbose
        for c in self._chromosomes:
            self._fitness(c.genes)
        # note that result is a list of best individual in each iteration
        self.result = []

    def _build_gene_bank(self):
        """
        Build bank of gene

        Returns: genes

        """
        gene_bank = GeneBank()
        for node in self._graph.nodes:
            gene_bank.genes.add(node)
        return gene_bank.genes

    def _init_chromosomes(self, max_chromosome_size, initial_gene_size):
        """
        Initial  chromosomes
        Args:
            max_chromosome_size: max size of genes can be loaded into chromosome
            initial_gene_size: size of chromosomes

        Returns: Generated chromosomes

        """
        chromosomes = []
        for i in range(max_chromosome_size):
            genes = list(np.random.choice(self._gene_bank, size=initial_gene_size, replace=False))
            chromosomes.append(Chromosome(genes))
        return chromosomes

    def _init_mutators(self):
        """

        Returns: mutator for evolution

        """
        mutators = list()
        mutators.append(AddMutator("Add mutator"))
        mutators.append(DeleteMutator("Delete mutator"))
        mutators.append(ChangeMutator("Change mutator"))
        return mutators

    def run(self, time_budget=0, iteration=0):
        """
        Main loop for MostCorrelation
        Args:
            time_budget: time for computation
            iteration: max iteration time

        Returns: best result

        """
        counter = 0
        start_time = time.time()
        while (time_budget != 0 and start_time + time_budget > time.time()) or (iteration != 0 and counter < iteration):
            self._chromosomes = self._evolve(self._chromosomes)
            for chromosome in self._chromosomes:
                self.result.append(copy.deepcopy(chromosome))
            if self.verbose:
                print(
                    f'time: {time.time()}, round:{counter}, best result:{sorted(self._chromosomes, key=lambda x: x.fitness)[0].fitness}')
            counter += 1
        return self.result

    def _evolve(self, chromosomes=[]):
        """
        iteration evolving
        Args:
            chromosomes: population

        Returns: generated chromosomes

        """
        for chromosome in chromosomes:
            chromosome.fitness = self._fitness(chromosome.genes)
        chromosomes = self._selection(chromosomes)
        chromosomes = self._crossover(chromosomes)
        self._mutation(chromosomes)
        for chromosome in chromosomes:
            chromosome.fitness = self._fitness(chromosome.genes)
        return chromosomes

    def _fitness(self, genes=[]):
        """
        Fitness function for chromosomes
        Args:
            genes: current population

        Returns: calculated chromosomes

        """
        edges = itertools.combinations(genes, 2)
        node_num = len(genes)
        fitness = 0
        for item in edges:
            try:
                fitness += float(self._graph[item[0]][item[1]]["weight"])
            except:
                pass
        fitness /= (
                (node_num - 1) * node_num)
        return fitness

    def _mutation(self, chromosomes=[]):
        """
        Apply mutation for chromosomes
        Args:
            chromosomes: chromosomes in current evolution

        Returns: mutated chromosomes

        """
        mutation_prob = 0.2
        for chromosome in chromosomes:
            if np.random.random() < mutation_prob:
                applied_mutators = []
                for mutator in self._mutators:
                    if mutator.can_mutate(self, chromosome):
                        applied_mutators.append(mutator)
                if len(applied_mutators) == 0:
                    continue
                mutator = np.random.choice(applied_mutators)
                chromosome.genes = mutator.mutate(self, chromosome)

    def _selection(self, chromosomes=[]):
        """
        Since there is no strategies for selection
        Args:
            chromosomes: current chromosomes

        Returns: selection of chromosomes

        """
        return chromosomes

    def _crossover(self, chromosomes=[]):
        """

        Args:
            chromosomes: list of chromosomes

        Returns: chromosomes after crossover

        """
        father = chromosomes[:len(chromosomes) // 2]
        mother = chromosomes[len(chromosomes) // 2:]
        crossover_range = min(len(father), len(mother))
        crossover_prob = 0.8
        new_population = []
        for i in range(crossover_range):
            if np.random.rand() < crossover_prob:
                son = mother[i]
                daughter = father[i]
            else:
                daughter = mother[i]
                son = father[i]
            new_population.append(son)
            new_population.append(daughter)
        # check if not even
        if len(chromosomes) % 2 == 1:
            rest = chromosomes[crossover_range << 1:]
            if np.random.rand() < 0.5:
                new_population.extend(rest)
            else:
                rest.extend(new_population)
                new_population = rest
        return new_population

    def get_result(self):
        """

        Returns: rest of MostCorrelation

        """
        iter_set = set()
        res = []
        for chromosome in self.result:
            elem = tuple(sorted(chromosome))
            if iter_set.__contains__(elem):
                continue
            res.append(chromosome)
            iter_set.add(elem)
        res = sorted(res, key=lambda x: -x.fitness)
        return [[gene for gene in item.genes] for item in res]


from dataclasses import dataclass,field

## TvShowTorrent is based on the record returned form 1337x
## It will parse the the record returned from 1337x API and
## Have utilities to standardize the actions on that return data

class TvTorrentParsingError(Exception):
    def __init__(self):
        self.msg = 'There was an error parsing the torrent API data'
        super().__init__(self.msg)

@dataclass
class TvShowTorrent():
    _name:str=field(init=False)
    _torrent_id:str=field(init=False)
    _link:str=field(init=False)
    _seeders:int=field(init=False)
    _leechers:str=field(init=False)
    _size:str=field(init=False)
    _upload_date:str=field(init=False)
    _uploader:str=field(init=False)
    _uploader_link:str=field(init=False)
    _adjusted_size:float=field(init=False)
    def __lt__(self, other):
        return self._adjusted_size < other.score

    def parse_data(self, torrent_record)->None:
        try:
            self._name=torrent_record['name']
            self._torrent_id=torrent_record['torrentId']
            self._link=torrent_record['link']
            self._seeders=int(torrent_record['seeders'] )
            self._leechers=torrent_record['leechers']
            self._size=torrent_record['size']
            self._upload_date=torrent_record['time']
            self._uploader=torrent_record['uploader']
            self._uploader_link=torrent_record['uploaderLink']
            self._adjusted_size=self.startard_sizes()
        except:
           raise TvTorrentParsingError()

    ## The size of the torrent files are on Mb and Gb but as strings and
    ## This will standardize them as Gb as a float
    def startard_sizes(self):
        size=float(self._size[0:len(self._size)-3])
        if self._size.upper().find('MB')!=-1:
            size=size/1000
        return size

    ## Takes a string of a show name, season and episode and returns
    ## True or False if it matches
    def matches(self,name:str,season:int,episode:int):
        ## Make it upper to match easier
        upper_show=self._name.upper().replace(' ','.')

        ## Make it upper to match easier
        search_show='{show}.S{season:02d}E{episode:02d}'.format(\
            show=name.upper(),season=season,episode=episode)

        if upper_show.find(search_show.upper().replace(' ','.'))==0:
            return True
        else:
            return False