from Bio import Entrez, SeqIO, pairwise2
from Bio.Seq import Seq
from Bio.pairwise2 import format_alignment
from Bio.codonalign.codonseq import CodonSeq, cal_dn_ds
import tools
from tabulate import tabulate
from Bio.Align import substitution_matrices
from Bio import Align
import pprint

#--------------------------------------global values-------------------------------------#
nextLatter={
    'A':'C', 'C':'G', 'G':'T', 'T':'A'
}

gencode = {
    'ATA':'I', 'ATC':'I', 'ATT':'I', 'ATG':'M',
    'ACA':'T', 'ACC':'T', 'ACG':'T', 'ACT':'T',
    'AAC':'N', 'AAT':'N', 'AAA':'K', 'AAG':'K',
    'AGC':'S', 'AGT':'S', 'AGA':'R', 'AGG':'R',
    'CTA':'L', 'CTC':'L', 'CTG':'L', 'CTT':'L',
    'CCA':'P', 'CCC':'P', 'CCG':'P', 'CCT':'P',
    'CAC':'H', 'CAT':'H', 'CAA':'Q', 'CAG':'Q',
    'CGA':'R', 'CGC':'R', 'CGG':'R', 'CGT':'R',
    'GTA':'V', 'GTC':'V', 'GTG':'V', 'GTT':'V',
    'GCA':'A', 'GCC':'A', 'GCG':'A', 'GCT':'A',
    'GAC':'D', 'GAT':'D', 'GAA':'E', 'GAG':'E',
    'GGA':'G', 'GGC':'G', 'GGG':'G', 'GGT':'G',
    'TCA':'S', 'TCC':'S', 'TCG':'S', 'TCT':'S',
    'TTC':'F', 'TTT':'F', 'TTA':'L', 'TTG':'L',
    'TAC':'Y', 'TAT':'Y', 'TAA':'_', 'TAG':'_',
    'TGC':'C', 'TGT':'C', 'TGA':'_', 'TGG':'W'}

#.........................................................................................#
#.........................................................................................#
#-----------------------------------------------------------------------------------------#

#--------------------------------------load data------------------------------------------#
def open_GB( GB_file_path):
    return tools.import_data_from_DB_file(GB_file_path)

def get_sequence(GB_file_path):
    return  GB_file_path[0].sequence

def get_protain(GB_file_path):
    GOA_dictionary_to_return,Name_dictionary_to_return,all_protein=tools.get_dictionary_of_protain_from_GB_file(GB_file_path)
    return all_protein
#------------------------------------------A----------------------------------------------#
def oneOfTheTwo(a,b,equal):
    if a==equal or b==equal:
        return True
    return False
def getN(kodon):# return how many mutations can be without the start or end kodons
    n=9
    if oneOfTheTwo('_','M',gencode[kodon[0:2]+nextLatter[kodon[2]]]):
        n-=1
    if oneOfTheTwo('_','M',gencode[kodon[0:2]+nextLatter[nextLatter[kodon[2]]]]):
        n-=1
    if oneOfTheTwo('_','M',gencode[kodon[0:2]+nextLatter[nextLatter[nextLatter[kodon[2]]]]]):
        n-=1

    #sec latter
    if oneOfTheTwo('_','M',gencode[kodon[0:1]+nextLatter[kodon[1]]+kodon[2]]):
        n-=1
    if oneOfTheTwo('_','M',gencode[kodon[0:1]+nextLatter[nextLatter[kodon[1]]]+kodon[2]]):
        n-=1
    if oneOfTheTwo('_','M',gencode[kodon[0:1]+nextLatter[nextLatter[nextLatter[kodon[1]]]]+kodon[2]]):
        n-=1

    #third latter
    if oneOfTheTwo('_','M',gencode[nextLatter[kodon[0]]+kodon[1:3]]):
        n-=1
    if oneOfTheTwo('_','M',gencode[nextLatter[nextLatter[kodon[0]]]+kodon[1:3]]):
        n-=1
    if oneOfTheTwo('_','M',gencode[nextLatter[nextLatter[nextLatter[kodon[0]]]]+kodon[1:3]]):
        n-=1

    return n
def countSynonymsAt(kodon):
    # count a certain unit how many synonyms he has
    count=0

    if gencode[kodon]==gencode[kodon[0:2]+nextLatter[kodon[2]]]:
        count += 1
    if gencode[kodon]==gencode[kodon[0:2]+nextLatter[nextLatter[kodon[2]]]]:
        count += 1
    if gencode[kodon]==gencode[kodon[0:2]+nextLatter[nextLatter[nextLatter[kodon[2]]]]]:
        count+=1

    #sec latter
    if gencode[kodon]==gencode[kodon[0:1]+nextLatter[kodon[1]]+kodon[2]]:
        count += 1
    if gencode[kodon]==gencode[kodon[0:1]+nextLatter[nextLatter[kodon[1]]]+kodon[2]]:
        count += 1
    if gencode[kodon]==gencode[kodon[0:1]+nextLatter[nextLatter[nextLatter[kodon[1]]]]+kodon[2]]:
        count+=1

    #third latter
    if gencode[kodon]==gencode[nextLatter[kodon[0]]+kodon[1:3]]:
        count += 1
    if gencode[kodon]==gencode[nextLatter[nextLatter[kodon[0]]]+kodon[1:3]]:
        count += 1
    if gencode[kodon]==gencode[nextLatter[nextLatter[nextLatter[kodon[0]]]]+kodon[1:3]]:
        count+=1

    return count*3/getN(kodon)

def countSynonyms():# return fs*3/n => synonyms count
    dic={}
    kodon="AAA"
    for i in range(4):
        for j in range(4):
            for k in range(4):
                dic[kodon]=countSynonymsAt(kodon)
                kodon=kodon[0:2]+nextLatter[kodon[2]]
            kodon=kodon[0] +nextLatter[kodon[1]]+kodon[2]
        kodon= nextLatter[kodon[0]]+kodon[1:3]
    return dic

#.........................................................................................#
#.........................................................................................#
#------------------------------------------B.a----------------------------------------------#
def count_equal_genes(genesA,genesB):
    count=0
    for i in range(len(genesA)):
        if genesA[i][0]==genesB[i][0]:
            count=+1
    return count

#.........................................................................................#
#------------------------------------------B.b----------------------------------------------#
def correctKodon(seq,gene):
    fixed_seq=""
    seq_counter=0
    for i in range(len(gene)):
        if gene[i]=='-':
            fixed_seq+='---'
        else:
            fixed_seq+=seq[seq_counter:seq_counter+3]
            seq_counter+=3
    return fixed_seq
    pass
def calculateDNDS(seqA,seqB,geneA,geneB):# return dn , ds , and dnds ratio
    aligner = Align.PairwiseAligner()
    aligner.substitution_matrix = substitution_matrices.load("BLOSUM62")

    alignments = aligner.align(geneA, geneB)
    string=str(alignments[0])

    index=string.find('\n')
    in_order_a=correctKodon(seqA,string[0:index])
    in_order_b=correctKodon(seqB,string[index*2+2:-1])
    dN, dS=cal_dn_ds(CodonSeq(in_order_a[0:min(len(in_order_a),len(in_order_b))]),CodonSeq(in_order_b[0:min(len(in_order_a),len(in_order_b))]))
    dN_dS_ratio=-1
    if dS!=0:
        dN_dS_ratio= float(dN/dS)
    return dN,dS,dN_dS_ratio
    pass

def getKODON(gene,data):# return the kodons that made the gene
    end=gene[1].end
    start=gene[1].start
    return data[start:end]
    pass

def getGeneNames(gene):# return names of the genes (remove dups as well)
    names=[]
    for i in range(len(gene)):
        gene_name=gene[i][2].qualifiers['gene'][0]
        if gene_name not in names:
            names.append(gene_name)
    return names
def compareOrganizm(genA,genB):# count how many proteins are equal
    equalGenes=0
    unqualGenes=0
    gen_names_A=getGeneNames(genA)
    gen_names_B = getGeneNames(genB)
    for i in range(min(len(gen_names_A),len(gen_names_B))):
        if gen_names_A[i]==gen_names_B[i]:
            equalGenes+=1
        else:
            unqualGenes+=1

    return equalGenes,unqualGenes

    pass

#.........................................................................................#
#.........................................................................................#
#.........................................................................................#
#-----------------------------------------------------------------------------------------#
def main_c():
    print()

    #load files
    data_corona=open_GB("corona_2020.gb")
    data_corona_recent=open_GB("corona_2022.gb")

    #     A      ----->
    dic=countSynonyms()
    count =0
    for sub_dict  in dic.keys():
        spaces=""
        num=str(round(float(dic[sub_dict]), 3))
        for i in range(5-len(num)):
            spaces=spaces+" "
        print(sub_dict+":"+num,end=spaces+"       ")
        count+=1
        if(count==4):
            count=0
            print()

    #     B.a    ----->
    print()
    genes_2020=get_protain("corona_2020.gb")
    genes_2022=get_protain("corona_2022.gb")

    count_of_equal_genes=count_equal_genes(genes_2020,genes_2022)
    print("exacaly same genes:",count_of_equal_genes)
    equalSum,unequalSum=compareOrganizm(genes_2020,genes_2022)
    print("same genes overall:",equalSum)
    if unequalSum==0:
        if equalSum!=count_of_equal_genes:
            print("both have the same genes, some proteins are different")
        else:
            print("both have the same exactly gene's")
    else:
        print("the amount of unequal genes:",unequalSum)

    #     B.b    ----->
    print()
    dndsList=[]
    toPrint=[[]*5]
    F=2
    T=7
    for i in range(F,T):
        dndsList.append(calculateDNDS(getKODON(genes_2020[i],get_sequence(data_corona)),getKODON(genes_2022[i],get_sequence(data_corona_recent)),genes_2020[i][0],genes_2022[i][0]))
        dN, dS, dN_dS_ratio = dndsList[i-F]
        gene=genes_2020[i][2].qualifiers['gene']
        locus_tag=genes_2020[i][2].qualifiers['locus_tag']
        protein_id=genes_2020[i][2].qualifiers['protein_id']
        product=genes_2020[i][2].qualifiers['product']
        Type=genes_2020[i][2].type
        selectionType=""

        if dN_dS_ratio>1.25 or dN_dS_ratio==-1:
            selectionType="Positive selection"
        elif dN_dS_ratio<0.75:
            selectionType="Negative selection"
        else:
            selectionType="Neutral selection"
        if dN_dS_ratio==-1:
            dN_dS_ratio="unvalid"
        toPrint.append([gene[0],locus_tag[0],protein_id[0],product[0],Type,dN,dS,dN_dS_ratio,selectionType])

    print(tabulate(toPrint, headers=['gene', 'locus_tag','protein_id','product','type','dN','dS','dN_dS_ratio','prefences'], tablefmt='orgtbl'))# print the information table

main_c()


import torch
import torch.nn as nn
import random

class MyEmbedding(nn.Module):

    def __init__(self, vocab_size: int, embedding_size: int):
        super(MyEmbedding, self).__init__()
        self.vocab_size = vocab_size
        self.embedding_size = embedding_size
        self.w_target = nn.Parameter(torch.randn(vocab_size, embedding_size), requires_grad=True)
        self.w_context = nn.Parameter(torch.randn(vocab_size, embedding_size), requires_grad=True)
        self.Softmax = nn.LogSoftmax(dim=0)

    def forward(self, X):

        X = torch.matmul(self.w_context, self.w_target[X].T)
        X = self.Softmax(X)
        X = X.view(1, -1)

        return X

class Node2Vec(object):

    def __init__(self, G, dimension, n_walks, walk_length, context_size, out_param, inout_param):
        self.G = G
        self.d = dimension
        self.r = n_walks
        self.l = walk_length
        self.k = context_size
        self.p = out_param
        self.q = inout_param
        self.walks = self.generate_walks()

    def generate_walks(self):
        '''
        Generate (N . r) random paths through the graph.

        :return: a list of paths.
        '''

        walks = []

        for i in range(self.r):
            nodes = list(self.G.keys())
            random.shuffle(nodes)

            for node in nodes:
                walk = self.bias_walk(self.G, node)
                walks.append(walk)

        return walks

    def bias_walk(self, G, node):
        '''
        traverses L nodes of the graph and adds them to a list of elements,
        composing the trajectory of the biased path.

        :param G (dict):
        :param node (int):

        :return:
        '''

        walk = [node]

        walk.append(random.choice(G[node]))

        for walk_iter in range(1, self.l):
            curr = walk[-1]
            previous = walk[-2]

            new_node = self.getNeighbors(curr, previous, G)
            walk.append(new_node)

        return walk

    def getNeighbors(self, curr, previous, G):

        transitionProbabilities = []

        for node in G[curr]:
            if node == previous:
                transitionProbabilities.append(1 / self.p)
            elif previous in G[node]:
                transitionProbabilities.append(1)
            else:
                transitionProbabilities.append(1 / self.q)

        selected_node = random.choices(G[curr], transitionProbabilities)[0]

        return selected_node

    def run(self):
        ''' turns random walks into central nodes along with their contexts. '''

        context_data = []

        for walk in self.walks:
            if len(walk) == len(set(walk)):

                center_pos = self.l // 2
                center = walk[center_pos]
                context = [(center, node) for i, node in enumerate(walk) if i != center_pos]

                context_data.extend(context)

        return context_data


from models.Wave import Waves
from models.Enemies.Piggy import Piggy
from models.Enemies.PigKnight import PigKnight
from models.Enemies.Iris import Iris
from models.Money import Money
from models.Map import Path
from pygame import Surface
from pygame.math import Vector2
from view.HealthBar import HealthBar

TIME_BEFORE_STARTING = 3
TIME_BETWEEN_SPAWNS = 0.5

class EventManager:
    def __init__(self, player):
        self.waveIndex = 0
        self.mobIndex = 0
        self.enemiesAlive = []
        self.player = player
        self.waitingTime = TIME_BEFORE_STARTING
        self.doneSpawningWaves = False
        self.playerHealthBar = HealthBar(Vector2(1280 * 0.5 - 250, 20), Vector2(500, 60))

    def update(self, dT: float):
        self.updateWaves(dT)
        self.updateEnemies(dT)
        self.playerHealthBar.update(self.player.hp, self.player.maxHealth)

    def updateWaves(self, dT: float):
        if(self.doneSpawningWaves): return

        self.waitingTime -= dT
        if(self.waitingTime > 0): return

        # Check if there are still mobs to spawn in the current wave
        wave: str = Waves[self.waveIndex]
        if(self.mobIndex < len(wave)):
            mobChar = wave[self.mobIndex]
            self.spawnEnemyFromChar(mobChar)
            self.mobIndex += 1
            self.waitingTime = TIME_BETWEEN_SPAWNS
        elif(len(self.enemiesAlive) == 0):
            self.waveIndex += 1

            # Check if it was the last wave
            if(self.waveIndex == len(Waves)):
                self.doneSpawningWaves = True

    def updateEnemies(self, dT: float):
        for enemy in self.enemiesAlive:
            enemy.update(dT)
            if(enemy.reachedEndOfPath):
                self.player.takeDamage(enemy.hp)
                self.enemiesAlive.remove(enemy)
            elif(enemy.dead):
                self.player.addMoney(enemy.dropMoney)
                self.enemiesAlive.remove(enemy)

    def spawnEnemyFromChar(self, char):
        if(char == 'P'):
            spawnPoint = Path[0].copy()
            piggy = Piggy(spawnPoint)
            self.enemiesAlive.append(piggy)
        if(char == 'K'):
            spawnPoint = Path[0].copy()
            pigknight = PigKnight(spawnPoint)
            self.enemiesAlive.append(pigknight)
        if(char == 'I'):
            spawnPoint = Path[0].copy()
            iris = Iris(spawnPoint)
            self.enemiesAlive.append(iris)

    def render(self, surf: Surface):
        for enemy in self.enemiesAlive:
            enemy.render(surf)
        self.playerHealthBar.render(surf)

    def skipWave(self):
        pass


import numpy as np
# import pylab
import matplotlib.cm as cm
import os
# import cv2

def make_overlay(image, gt_prob):

    mycm = cm.get_cmap('bwr')

    overimage = mycm(gt_prob, bytes=True)
    output = 0.4*overimage[:, :, 0:3] + 0.6*image

    return output

def overlayImageWithConfidence(in_image, conf, vis_channel = 1, threshold = 0.5):
    '''

    :param in_image:
    :param conf:
    :param vis_channel:
    :param threshold:
    '''
    if in_image.dtype == 'uint8':
        visImage = in_image.copy().astype('f4')/255
    else:
        visImage = in_image.copy()

    channelPart = visImage[:, :, vis_channel] * (conf > threshold) - conf
    channelPart[channelPart < 0] = 0
    visImage[:, :, vis_channel] = 0.5*visImage[:, :, vis_channel] + 255*conf
    return visImage

def evalExp(hypes, gt_image, cnn_image):
    '''
    Does the basic pixel based evaluation!
    '''

    assert len(gt_image.shape) == 3, 'Wrong size of input prob map'
    assert len(cnn_image.shape) == 3, 'Wrong size of input prob map'

    head = hypes['arch']['weight']
    shape = gt_image.shape
    cnn_image_argmax = np.argmax(cnn_image, axis=2)
    gt_image_argmax = np.argmax(gt_image, axis=2)

    # pixel accuracy / mean accuracy / mean IoU / weighted mean IoU
    intersection_class = np.zeros(shape[2])
    unionsection_class = np.zeros(shape[2])
    pixel_class = np.zeros(shape[2])
    #cnn_image_class = np.zeros(shape[0],shape[1])

    for i in np.arange(shape[2]):
        cnn_image_class = np.all(cnn_image_argmax.reshape(shape[0], shape[1], 1) == i, axis=2)
        intersection_class[i] = np.sum(cnn_image_class * gt_image[:, :, i])
        pixel_class[i] = np.sum(gt_image[:, :, i])
        unionsection_class[i] = pixel_class[i] + np.sum(cnn_image_class) - intersection_class[i]

    return intersection_class, pixel_class, unionsection_class

def pxEval_maximizeFMeasure(totalPosNum, totalNegNum, totalFN, totalFP, thresh = None):
    '''

    @param totalPosNum: scalar
    @param totalNegNum: scalar
    @param totalFN: vector
    @param totalFP: vector
    @param thresh: vector
    '''

    #Calc missing stuff
    totalTP = totalPosNum - totalFN
    totalTN = totalNegNum - totalFP

    valid = (totalTP>=0) & (totalTN>=0)
    assert valid.all(), 'Detected invalid elements in eval'

    recall = totalTP / float( totalPosNum )
    TNR    = totalTN / float( totalNegNum )
    precision =  totalTP / (totalTP + totalFP + 1e-10)

    accuracy = (totalTP + totalTN) / (float( totalPosNum ) + float( totalNegNum ))

    selector_invalid = (recall==0) & (precision==0)
    recall = recall[~selector_invalid]
    precision = precision[~selector_invalid]

    maxValidIndex = len(precision)

    #Pascal VOC average precision
    AvgPrec = 0
    counter = 0
    for i in np.arange(0,1.1,0.1):
        ind = np.where(recall>=i)
        if ind == None:
            continue
        pmax = max(precision[ind])
        AvgPrec += pmax
        counter += 1
    AvgPrec = AvgPrec/counter

    # F-measure operation point
    beta = 1.0
    betasq = beta**2
    F = (1 + betasq) * (precision * recall)/((betasq * precision) + recall + 1e-10)
    index = F.argmax()
    MaxF= F[index]

    recall_bst = recall[index]
    precision_bst =  precision[index]

    TP = totalTP[index]
    TN = totalTN[index]
    FP = totalFP[index]
    FN = totalFN[index]
    valuesMaxF = np.zeros((1,4),'u4')
    valuesMaxF[0,0] = TP
    valuesMaxF[0,1] = TN
    valuesMaxF[0,2] = FP
    valuesMaxF[0,3] = FN

    #ACC = (totalTP+ totalTN)/(totalPosNum+totalNegNum)
    prob_eval_scores  = calcEvalMeasures(valuesMaxF)
    prob_eval_scores['AvgPrec'] = AvgPrec
    prob_eval_scores['MaxF'] = MaxF
    prob_eval_scores['accuracy'] = accuracy

    #prob_eval_scores['totalFN'] = totalFN
    #prob_eval_scores['totalFP'] = totalFP
    prob_eval_scores['totalPosNum'] = totalPosNum
    prob_eval_scores['totalNegNum'] = totalNegNum

    prob_eval_scores['precision'] = precision
    prob_eval_scores['recall'] = recall
    prob_eval_scores['TNR'] = TNR
    #prob_eval_scores['precision_bst'] = precision_bst
    #prob_eval_scores['recall_bst'] = recall_bst
    prob_eval_scores['thresh'] = thresh
    if thresh is not None:
        BestThresh= thresh[index]
        prob_eval_scores['BestThresh'] = BestThresh

    #return a dict
    return prob_eval_scores

def pxEval(total_intersection_class, total_pixel_class, total_unionsection_class):

    '''
    @param total_intersection_class: vector
    @param total_pixel_class: vector
    @param total_unionsection_class: vector
    '''

    assert len(total_intersection_class.shape) == 1, 'Wrong size of input prob map'
    assert len(total_pixel_class.shape) == 1, 'Wrong size of input prob map'
    assert len(total_unionsection_class.shape) == 1, 'Wrong size of input prob map'

    pixel_sum = np.sum(total_pixel_class)

    prob_eval_scores = dict()

    prob_eval_scores['PixelAcc'] = np.float32(np.sum(total_intersection_class))/pixel_sum
    prob_eval_scores['MeanPixelAcc'] = \
        np.mean(np.float32(total_intersection_class)/total_pixel_class)
    prob_eval_scores['MeanIoU'] = \
        np.mean(np.float32(total_intersection_class)/total_unionsection_class)
    prob_eval_scores['FwMeanIoU'] = \
        np.sum(np.float32(total_intersection_class*total_pixel_class)/total_unionsection_class)/pixel_sum

    # prob_eval_scores['PixelAcc'] = np.sum(total_intersection_class)
    # prob_eval_scores['MeanPixelAcc'] = np.sum(total_pixel_class)
    # prob_eval_scores['MeanIoU'] = np.sum(total_unionsection_class)
    # prob_eval_scores['FwMeanIoU'] = np.sum(total_unionsection_class)

    # return a dict
    return prob_eval_scores

def calcEvalMeasures(evalDict, tag  = '_wp'):
    '''

    :param evalDict:
    :param tag:
    '''
    # array mode!
    TP = evalDict[:,0].astype('f4')
    TN = evalDict[:,1].astype('f4')
    FP = evalDict[:,2].astype('f4')
    FN = evalDict[:,3].astype('f4')
    Q = TP / (TP + FP + FN)
    P = TP + FN
    N = TN + FP
    TPR = TP / P
    FPR = FP / N
    FNR = FN / P
    TNR = TN / N
    A = (TP + TN) / (P + N)
    precision = TP / (TP + FP)
    recall = TP / P
    #numSamples = TP + TN + FP + FN
    correct_rate = A

    # F-measure
    #beta = 1.0
    #betasq = beta**2
    #F_max = (1 + betasq) * (precision * recall)/((betasq * precision) + recall + 1e-10)

    outDict =dict()

    outDict['TP'+ tag] = TP
    outDict['FP'+ tag] = FP
    outDict['FN'+ tag] = FN
    outDict['TN'+ tag] = TN
    outDict['Q'+ tag] = Q
    outDict['A'+ tag] = A
    outDict['TPR'+ tag] = TPR
    outDict['FPR'+ tag] = FPR
    outDict['FNR'+ tag] = FNR
    outDict['PRE'+ tag] = precision
    outDict['REC'+ tag] = recall
    outDict['correct_rate'+ tag] = correct_rate
    return outDict

def setFigLinesBW(fig):
    """
    Take each axes in the figure, and for each line in the axes, make the
    line viewable in black and white.
    """
    for ax in fig.get_axes():
        setAxLinesBW(ax)

def setAxLinesBW(ax):
    """
    Take each Line2D in the axes, ax, and convert the line style to be
    suitable for black and white viewing.
    """
    MARKERSIZE = 3

#     COLORMAP = {
#         'r': {'marker': None, 'dash': (None,None)},
#         'g': {'marker': None, 'dash': [5,2]},
#         'm': {'marker': None, 'dash': [11,3]},
#         'b': {'marker': None, 'dash': [6,3,2,3]},
#         'c': {'marker': None, 'dash': [1,3]},
#         'y': {'marker': None, 'dash': [5,3,1,2,1,10]},
#         'k': {'marker': 'o', 'dash': (None,None)} #[1,2,1,10]}
#         }
    COLORMAP = {
        'r': {'marker': "None", 'dash': ("None","None")},
        'g': {'marker': "None", 'dash': [5,2]},
        'm': {'marker': "None", 'dash': [11,3]},
        'b': {'marker': "None", 'dash': [6,3,2,3]},
        'c': {'marker': "None", 'dash': [1,3]},
        'y': {'marker': "None", 'dash': [5,3,1,2,1,10]},
        'k': {'marker': 'o', 'dash': ("None","None")} #[1,2,1,10]}
        }

    for line in ax.get_lines():
        origColor = line.get_color()
        #line.set_color('black')
        line.set_dashes(COLORMAP[origColor]['dash'])
        line.set_marker(COLORMAP[origColor]['marker'])
        line.set_markersize(MARKERSIZE)

def plotPrecisionRecall(precision, recall, outFileName, Fig=None, drawCol=1, textLabel = None, title = None, fontsize1 = 24, fontsize2 = 20, linewidth = 3):
    '''

    :param precision:
    :param recall:
    :param outFileName:
    :param Fig:
    :param drawCol:
    :param textLabel:
    :param fontsize1:
    :param fontsize2:
    :param linewidth:
    '''

    clearFig = False

    if Fig == None:
        Fig = pylab.figure()
        clearFig = True

    #tableString = 'Algo avgprec Fmax prec recall accuracy fpr Q(TonITS)\n'
    linecol = ['g','m','b','c']
    #if we are evaluating SP, then BL is available
    #sectionName = 'Evaluation_'+tag+'PxProb'
    #fullEvalFile = os.path.join(eval_dir,evalName)
    #Precision,Recall,evalString = readEvaluation(fullEvalFile,sectionName,AlgoLabel)

    pylab.plot(100*recall, 100*precision, linewidth=linewidth, color=linecol[drawCol], label=textLabel)

    #writing out PrecRecall curves as graphic
    setFigLinesBW(Fig)
    if textLabel!= None:
        pylab.legend(loc='lower left',prop={'size':fontsize2})

    if title!= None:
        pylab.title(title, fontsize=fontsize1)

    #pylab.title(title,fontsize=24)
    pylab.ylabel('PRECISION [%]',fontsize=fontsize1)
    pylab.xlabel('RECALL [%]',fontsize=fontsize1)

    pylab.xlim(0,100)
    pylab.xticks( [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100],
                      ('0','','20','','40','','60','','80','','100'), fontsize=fontsize2 )
    pylab.ylim(0,100)
    pylab.yticks( [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100],
                      ('0','','20','','40','','60','','80','','100'), fontsize=fontsize2 )
    pylab.grid(True)

    #
    if type(outFileName) != list:
        pylab.savefig( outFileName )
    else:
        for outFn in outFileName:
            pylab.savefig( outFn )
    if clearFig:
        pylab.close()
        Fig.clear()

def saveBEVImageWithAxes(data, outputname, cmap = None, xlabel = 'x [m]', ylabel = 'z [m]', rangeX = [-10, 10], rangeXpx = None, numDeltaX = 5, rangeZ = [7, 62], rangeZpx = None, numDeltaZ = 5, fontSize = 16):
    '''

    :param data:
    :param outputname:
    :param cmap:
    '''
    aspect_ratio = float(data.shape[1])/data.shape[0]
    fig = pylab.figure()
    Scale = 8
    # add +1 to get axis text
    fig.set_size_inches(Scale*aspect_ratio+1,Scale*1)
    ax = pylab.gca()
    #ax.set_axis_off()
    #fig.add_axes(ax)
    if cmap != None:
        pylab.set_cmap(cmap)

    #ax.imshow(data, interpolation='nearest', aspect = 'normal')
    ax.imshow(data, interpolation='nearest')

    if rangeXpx == None:
        rangeXpx = (0, data.shape[1])

    if rangeZpx == None:
        rangeZpx = (0, data.shape[0])

    modBev_plot(ax, rangeX, rangeXpx, numDeltaX, rangeZ, rangeZpx, numDeltaZ, fontSize, xlabel = xlabel, ylabel = ylabel)
    #plt.savefig(outputname, bbox_inches='tight', dpi = dpi)
    pylab.savefig(outputname, dpi = data.shape[0]/Scale)
    pylab.close()
    fig.clear()

def modBev_plot(ax, rangeX = [-10, 10 ], rangeXpx= [0, 400], numDeltaX = 5, rangeZ= [8,48 ], rangeZpx= [0, 800], numDeltaZ = 9, fontSize = None, xlabel = 'x [m]', ylabel = 'z [m]'):
    '''

    @param ax:
    '''
    #TODO: Configureabiltiy would be nice!
    if fontSize==None:
        fontSize = 8

    ax.set_xlabel(xlabel, fontsize=fontSize)
    ax.set_ylabel(ylabel, fontsize=fontSize)

    zTicksLabels_val = np.linspace(rangeZpx[0], rangeZpx[1], numDeltaZ)
    ax.set_yticks(zTicksLabels_val)
    #ax.set_yticks([0, 100, 200, 300, 400, 500, 600, 700, 800])
    xTicksLabels_val = np.linspace(rangeXpx[0], rangeXpx[1], numDeltaX)
    ax.set_xticks(xTicksLabels_val)
    xTicksLabels_val = np.linspace(rangeX[0], rangeX[1], numDeltaX)
    zTicksLabels = map(lambda x: str(int(x)), xTicksLabels_val)
    ax.set_xticklabels(zTicksLabels,fontsize=fontSize)
    zTicksLabels_val = np.linspace(rangeZ[1],rangeZ[0], numDeltaZ)
    zTicksLabels = map(lambda x: str(int(x)), zTicksLabels_val)
    ax.set_yticklabels(zTicksLabels,fontsize=fontSize)


import time
from tabulate import tabulate
import math
from function import *
from tree import *
from instance import *
from semilinear import *
from linearset import *
from functools import reduce
from proof import buildProof
from proof import buildReachProof
from math import gcd
from errors import *

from tool import buildinv

#methods to invoke the code and make the output pretty

def appen(txt, *vartuple):
	vars = list(vartuple)
	txt = txt + " ".join([str(x) for x in vars])
	txt += "\n"
	return txt

def breaker(txt):
	return appen(txt, "-----------------")

## Print the output of manual (the response dictionary) in a pretty(ish) way
def pyprint(data):
	txt = ""
	txt = breaker(txt)
	txt = appen(txt, "Interpretation of input")
	txt = appen(txt, "start:", data['start'],'target:', data['target'], "functions:", data['functions'])
	txt = breaker(txt)
	if data['everything']:
		txt = appen(txt, "invariant:",'ALL')
	txt = appen(txt, "invariant:",data['inv'])
	if 'errors' in data:
		txt = breaker(txt)
		txt = appen(txt, "errors:" ,data['errors'])
	txt = breaker(txt)
	txt = appen(txt, 'reachability:',  "reachable" if data['reachable'] else "unreachable")

	if data['reachable']:
		txt = appen(txt, 'target', data['target'], 'in', data['targetmember'])
		txt = appen(txt, 'proof of reachability:', data['por'])
	else:
		txt = appen(txt, 'target', data['target'], 'disjoint from invariant')

	if 'expectation' in data:
		txt = appen(txt, 'expecting:', "reachable" if data['expectation'] == True else "unreachable")
		txt = appen(txt, 'Expectation=Reality:', data['passtest'])

	txt = breaker(txt)
	txt = appen(txt, "Proof of invariance")
	txt = appen(txt, data['proof'])
	txt = breaker(txt)
	for x in data['time']:
		txt = appen(txt, 'time', x, f"{data['time'][x]:.9f}")
	txt = breaker(txt)
	print(txt)
	return txt

## interpret the input
def service(data):
	x= data['val']
	print(x)
	functions = []
	for i, line in enumerate(x.splitlines()):
		if line.startswith("ENDS"):
			break
		print(line)
		split = line.split(" ")
		if i == 0:
			start = int(split[0])

			targetStr = split[1]
			if '+' in targetStr:
				target = linearset(int(targetStr.split('+')[0]), int(targetStr.split('+')[1]),-1)
			else:
				target = linearset(int(split[1]))

			if len(split) > 2:
				expectation = split[2] == 'True'
			else:
				expectation = None
		else:
			functions.append(function(int(split[0]),int(split[1])))
	print(start,target,functions)
	inst = instance(start,target,functions)
	inst.setExp(expectation)
	return manual(inst)

### Take an instance and run it, build a response dictionary
def manual(inst,reachtimeout = 30):
	start,target,functions,expectation = inst.asTuple()

	start_time = time.time()
	semi, start,target,functions = buildinv(start,target,functions)
	end_time = time.time()

	errors = Errors()
	print(semi)
	semi.reduction()

	print('reality:', semi.containsFuzz(target) , 'expectations:', expectation)

	data = {
		'start': start,
		'target': target,
		'functions': functions,
		'instance' : instance,
		'inv' : semi,
		'everything': semi.weaklyIsEverything(),
		'time': {
			'invariant': end_time - start_time
		}
	}
	start_time = time.time()
	data['proof'] =  tabulate(buildProof(semi, functions, errors), headers =["Set", "under", "gives", "","within"])
	end_time = time.time()
	data['time']['proofOfInvariant'] = end_time - start_time

	data['reachable'] = semi.containsFuzz(target)

	if  semi.containsFuzz(target):
		data['targetmember'] = semi.getContainsFuzz(target)
		if target.isSingleton():
			if reachtimeout > 0:
				start_time = time.time()
				reachproof = buildReachProof(start,target,semi,functions, errors,reachtimeout)
				end_time = time.time()
				if len(reachproof) > 50:
					data['por']= " -> ".join([str(x) for x in reachproof[:5] + [".... stuff ...."] + reachproof[-5:]])
				else:
					data['por']= " -> ".join([str(x) for x in reachproof])
				data['time']['proofOfReachability'] = end_time - start_time

			else:
				data['por'] = "reachability proof disabled"
				data['time']['proofOfReachability'] = 0
		else:
			data['por'] = "reachability of periodic target not supported"
			data['time']['proofOfReachability'] = 0

	if expectation!=None:
		data['passtest'] = semi.containsFuzz(target) == expectation
		data['expectation'] = expectation
	if errors.hasErrors():
		data['errors'] = str(errors.getErrors())
	print(data)
	return data

import sys
if __name__ == "__main__":
	if len(sys.argv) > 1 :
		with open(sys.argv[1], "r")  as f:
			stuff = f.read()
			data = {}
			print(stuff)
			data['val']  = stuff
			pyprint(service(data))


from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import *
from PyQt5.uic import loadUiType
import sqlite3
import sys

from dopclass import MainAppMenu

students_ui, _= loadUiType('students.ui')
class MainAppStudents(QMainWindow,students_ui):

    def __init__(self, parent=None):
        super(MainAppStudents, self).__init__(parent)
        QMainWindow.__init__(self)
        self.setupUi(self)
        self.db_connect()
        self.handles_btn()

        self.table_st.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
        self.table_st.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Fixed)
        self.setWindowIcon(QtGui.QIcon('C:/Users/natal/OneDrive/Рабочий стол/Code_rename/photo/graduated.png'))
        self.move(QApplication.desktop().screen().rect().center()- self.rect().center())
        self.table_st.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch) #авт.растягивание колонок

        self.loaddata()

    def db_connect(self):
        self.db = sqlite3.connect('db.sqlite')
        self.cur = self.db.cursor()
        if(self.cur):
            self.messagebox("Сообщение проверки", "База данных db подключена")
        else:
            self.messagebox("Сообщение проверки", "База данных db не подключена")

    def messagebox(self, title, message):
            mess = QtWidgets.QMessageBox()
            mess.setWindowTitle(title)
            mess.setText(message)
            mess.setStandardButtons(QtWidgets.QMessageBox.Ok)
            mess.setWindowIcon(QtGui.QIcon('C:/Users/natal/OneDrive/Рабочий стол/Code_rename/photo/message.png'))
            mess.exec_()

    def handles_btn(self):
            self.btn_Insert_st.clicked.connect(self.Add_Students)
            self.Search_btn_st.clicked.connect(self.Search_Students)
            self.deletezapic_st.clicked.connect(self.Delete_Students)
            self.clears_st.clicked.connect(self.Clear)
            self.update_st.clicked.connect(self.Update_Students)
            self.vuvod_st.clicked.connect(self.loaddata)
            self.cleant_st.clicked.connect(self.clear_table)

            self.student_btn.clicked.connect(self.open_main_menu)

    def open_main_menu(self):
        self.close()
        self.main_menu = MainAppMenu()
        self.main_menu.show()

    def Add_Students(self):
        ID = self.lineEdit_2_st.text()
        surname = self.lineEdit_3_st.text()
        name = self.lineEdit_4_st.text()
        patronymic = self.lineEdit_st.text()
        gender = self.lineEdit_6_st_2.text()
        number = self.lineEdit_6_st.text()
        id_groupe = self.lineEdit_5_st.text()
        mail = self.lineEdit_7_st.text()
        phone = self.lineEdit_8_st.text()

        if ID and surname and name and patronymic and gender and number and id_groupe and mail and phone != "":
            self.cur.execute('SELECT * FROM students WHERE ID=?', (ID,))
            data = self.cur.fetchone()
            if data:
                self.messagebox("Ошибка", "Запись с таким ID уже существует")
            else:
                self.cur.execute('''
                    INSERT INTO students (ID, surname, name, patronymic, gender, number, id_groupe, mail, phone)
                    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
                ''', (ID, surname, name, patronymic, gender, number, id_groupe, mail, phone))
                self.db.commit()
                self.messagebox("Сообщение", "Запись группы была успешно добавлена")
                self.loaddata()
        else:
            self.messagebox("Сообщение", "Введите данные для записи в базу данных")

    def Search_Students(self):
        ID = self.lineEdit_seatch_st.text()
        sql =('''SELECT * FROM students WHERE ID=? ''')
        self.cur.execute(sql,[(ID)])
        data = self.cur.fetchone()
        if ID=="":
             self.messagebox("Сообщение", "Введите ID для поиска записи")
        elif (data):
                self.lineEdit_2_st.setText(str(data[0]))
                self.lineEdit_3_st.setText(str(data[1]))
                self.lineEdit_4_st.setText(str(data[2]))
                self.lineEdit_st.setText(str(data[3]))
                self.lineEdit_6_st_2.setText(str(data[4]))
                self.lineEdit_6_st.setText(str(data[5]))
                self.lineEdit_5_st.setText(str(data[6]))
                self.lineEdit_7_st.setText(str(data[7]))
                self.lineEdit_8_st.setText(str(data[8]))
        else:
                self.messagebox("Сообщение", "Нет записи в БД")

    def Delete_Students(self):
        ID = self.lineEdit_2_st.text()
        sql = 'DELETE FROM students WHERE ID=?'
        self.cur.execute(sql, [(ID)])
        self.db.commit()
        self.loaddata()
        self.Clear()

    def Clear(self):
        self.lineEdit_2_st.setText("")
        self.lineEdit_3_st.setText("")
        self.lineEdit_4_st.setText("")
        self.lineEdit_st.setText("")
        self.lineEdit_6_st_2.setText("")
        self.lineEdit_6_st.setText("")
        self.lineEdit_5_st.setText("")
        self.lineEdit_7_st.setText("")
        self.lineEdit_8_st.setText("")

        self.lineEdit_seatch_st.setText("")

    def clear_table(self):

        self.table_st.setRowCount(0)

    def Update_Students(self):
        ID = self.lineEdit_2_st.text()
        if(ID):
            ID = self.lineEdit_2_st.text()
            surname = self.lineEdit_3_st.text()
            name = self.lineEdit_4_st.text()
            patronymic = self.lineEdit_st.text()
            gender = self.lineEdit_6_st_2.text()
            number = self.lineEdit_6_st.text()
            id_groupe = self.lineEdit_5_st.text()
            mail = self.lineEdit_7_st.text()
            phone = self.lineEdit_8_st.text()

            self.cur.execute('''
                                UPDATE students SET ID=?, surname=?, name=?, patronymic=?,  gender=?, number=?, id_groupe=?,  mail=?,  phone=? WHERE ID=?
                                ''',(ID, surname, name,  patronymic, gender,  number, id_groupe, mail, phone,ID))
            self.db.commit()
            self.Clear()
            self.messagebox("Сообщение", "Запись обновлена")
            self.loaddata()
        else:
            self.messagebox("Сообщение", "Запись не обновлена")

    def loaddata(self):
        self.cur.execute('SELECT * FROM students LIMIT 300')
        rows = self.cur.fetchall()
        self.table_st.setRowCount(len(rows))
        for row_num, row_data in enumerate(rows):
            for col_num, data in enumerate(row_data):
                self.table_st.setItem(row_num, col_num, QtWidgets.QTableWidgetItem(str(data)))
        self.table_st.resizeColumnsToContents()

def main():
    app = QApplication(sys.argv)
    window = MainAppStudents()
    window.setFixedSize(1702, 903)
    window.show()
    app.exec_()

if __name__ == '__main__':
    main()


import numpy as np
import matplotlib.pyplot as pl
import pandas as pd
from scipy.integrate import solve_ivp
from scipy import interpolate
from scipy.integrate import quad
from matplotlib.widgets import Slider
from matplotlib.widgets import CheckButtons
from scipy.special import spherical_jn

def tau_dot(r_b0, a):
    Y_p = 0.24
    cut_off = 3.0
    dtau_dt = 2.3e-5 * h * r_b0 * (1 - Y_p/2) / a**2
    if dtau_dt>cut_off:
        dtau_dt = cut_off
    return -dtau_dt

def integrand(a, r_v0, r_m0, r_r0, r_c0):
    E = (q**2 + (a*m_v/T_nv0)**2)**0.5
    p_h = T_nv0**4 * (boole(q, q**2 * E * f0)) / (np.pi**2 * a**4)
    temp = (r_v0 + r_m0/a**3 + r_r0/a**4 + r_c0/a**2 + p_h / p_crit )
    return 1/ (a**2 * temp**0.5)

def boole(X,Y):
    h=X[1]-X[0]
    return 2*h*(7*(Y[0]+Y[-1])+32*np.sum(Y[1:-1:2])+12*np.sum(Y[2:-1:4])+14*np.sum(Y[4:-1:4]))/45.0

def func(a, y, K, r_v0, r_m0, r_cdm0, r_b0, r_r0, r_g0, r_nv0, r_c0):
    #print(a)
    #print(y)
    E = (q**2 + (a*m_v/T_nv0)**2)**0.5
    p_h = T_nv0**4 * (boole(q, q**2 * E * f0)) / (np.pi**2 * a**4) #eV^4 / (h_cross c)^3
    temp = (r_v0 + r_m0/a**3 + r_r0/a**4 + r_c0/a**2 + p_h / p_crit)
    #print(p_h / p_crit)
    #print(temp)
    R = (3 * a * r_b0) / (4*r_g0)

    conf_time = quad(integrand, 0, a, args=(r_v0, r_m0, r_r0, r_c0))[0] #in units of hubble time

    int = boole(q, q**2 * E * f0 * np.array(y[5+n_g_moments+n_nv_moments : 5+n_g_moments+n_nv_moments+len(q)]) )
    delta_h = T_nv0**4 * int / (np.pi**2 * a**4 * p_crit)

    int = boole(q, q**4 * f0 * np.array(y[5+n_g_moments+n_nv_moments+2*len(q) : 5+n_g_moments+n_nv_moments+3*len(q)]) / E )
    sigma_h = T_nv0**4 * int / (np.pi**2 * p_crit)
    #print(delta_h, sigma_h)

    #print( r_g0 * y[7], r_nv0 * y[7+n_g_moments], sigma_h )
    psi = -3.0 * ( r_nv0 * y[7+n_g_moments] + sigma_h) / ((a * K)**2) - y[0]
    phi_dot = psi/a + ( - (K)**2 * y[0] + 1.5 * a**2 * (r_cdm0*y[1]/a**3 + r_b0*y[3]/a**3 + r_g0*y[5]/a**4 + r_nv0*y[5+n_g_moments]/a**4 + delta_h ) ) / ( 3 * a**3 * temp )
    #print(psi)
    delta_cdm_dot = -3*phi_dot + K*y[2] / ( a**2 * temp**0.5 )
    v_cdm_dot = -y[2]/a - (K) * psi / ( a**2 * temp**0.5 )

    delta_b_dot = -3*phi_dot + K*y[4] / ( a**2 * temp**0.5 )

    v_b_dot = 0.0
    f_g = []
    f_g.append(- (K) * y[6] / ( a**2 * temp**0.5 ) - 4 * phi_dot)

    if a<1/1101.0:
        v_b_dot = -y[4]/a + ( -(K) * psi + c*tau_dot(r_b0, a) * (y[4] + 3 * y[6]/4 )/R ) / ( a**2 * temp**0.5 )
        f_g.append( ( (K/3.0) * (y[5] -2*y[7] + 4*psi) + c*tau_dot(r_b0, a) * (y[6] + 4*y[4]/3.0) ) / ( a**2 * temp**0.5 ) )
        for i in range(2, n_g_moments):
            if i==(n_g_moments-1):
                f_g.append( ( K * y[5+i-1] - (i+1) * y[5+i] / conf_time + c*tau_dot(r_b0, a)*y[5+i] ) / ( a**2 * temp**0.5 ) )
            else:
                f_g.append( ( (K/(2*i+1)) * (i * y[5+i-1] - (i+1) * y[5+i+1]) + c*tau_dot(r_b0, a)*y[5+i] ) / ( a**2 * temp**0.5 ) )
    else:
        v_b_dot = -y[4]/a + ( -(K) * psi ) / ( a**2 * temp**0.5 )
        f_g.append( ( (K/3.0) * (y[5] -2*y[7] + 4*psi) ) / ( a**2 * temp**0.5 ) )
        for i in range(2, n_g_moments):
            if i==(n_g_moments-1):
                f_g.append( ( K * y[5+i-1] - (i+1) * y[5+i] / conf_time ) / ( a**2 * temp**0.5 ) )
            else:
                f_g.append( ( (K/(2*i+1)) * (i * y[5+i-1] - (i+1) * y[5+i+1]) ) / ( a**2 * temp**0.5 ) )

    f_nv = []
    f_nv.append(- (K) * y[6+n_g_moments] / ( a**2 * temp**0.5 ) - 4 * phi_dot)
    f_nv.append( ( (K/3.0) * (y[5+n_g_moments] -2*y[7+n_g_moments] + 4*psi) ) / ( a**2 * temp**0.5 ) )
    for i in range(2, n_nv_moments):
        if i==(n_nv_moments-1):
            f_nv.append( K * (y[5+n_g_moments+i-1] - (i+1)*y[5+n_g_moments+i]/(K*conf_time) ) / ( a**2 * temp**0.5 ) )
            #f_nv.append( ( (K/(2*i+1)) * (i * y[5+n_g_moments+i-1] - (i+1) * 2 * spherical_jn(i+1, K*conf_time) / (1 + 0.4*r_nv0/r_r0) ) ) / ( a**2 * temp**0.5 ) )
        else:
            f_nv.append( ( (K/(2*i+1)) * (i * y[5+n_g_moments+i-1] - (i+1) * y[5+n_g_moments+i+1]) ) / ( a**2 * temp**0.5 ) )

    Psi_h_0, Psi_h_1, Psi_h_2 = [], [], []
    for i in range(len(q)):
        Psi_h_0.append( -q[i] * K * y[5 + n_g_moments + n_nv_moments+len(q)+i] / (E[i] * a**2 * temp**0.5) + phi_dot * dlnf_dlnq[i])
        Psi_h_1.append( ( q[i] * K * (y[5+n_g_moments+n_nv_moments+i] - 2 * y[5+n_g_moments+n_nv_moments+2*len(q)+i]) / (3 * E[i]) + E[i] * np.exp(q[i]) * f0[i] * K * psi / (3) ) / (a**2 * temp**0.5) )
        Psi_h_2.append( K * (q[i] * y[5+n_g_moments+n_nv_moments+len(q)+i] / E[i] - 3 * y[5+n_g_moments+n_nv_moments+2*len(q)+i]/(K*conf_time) ) / ( a**2 * temp**0.5 ) )

    dydt = [phi_dot, delta_cdm_dot, v_cdm_dot, delta_b_dot, v_b_dot] + f_g + f_nv + Psi_h_0 + Psi_h_1 + Psi_h_2
    #print(dydt)
    #input('LOL')
    return dydt

def expint(a, r_v0, r_m0, r_r0, r_c0):
    return quad(integrand, 0, a, args=(r_v0, r_m0, r_r0, r_c0))[0]

def solve(K, r_v0, r_m0, r_cdm0, r_b0, r_r0, r_g0, r_nv0, r_c0, start, end):
    a = 10**np.linspace(start,end,1000, endpoint=True)
    conf_time = quad(integrand, 0, a[0], args=(r_v0, r_m0, r_r0, r_c0))[0] #in units of hubble time
    #print(conf_time)

    #Initial Conditions
    #const = 10**(-9.3)
    #phi = (50 * np.pi**2 * (K)**(n-1)/ (9*(K/c)**3) * (2*np.pi)**3 * const)**0.5
    phi = 1.0
    psi = - phi / (1 + 0.4 * (conf_time/a[0])**2 * ( r_nv0 + T_nv0**4 * boole(q, q**4 * f0**2 * np.exp(q)) / (4*np.pi**2*p_crit) ) )
    #print(T0**4 * boole(q, q**4 * f0**2 * np.exp(q)) / (4*np.pi**2*p_crit))
    #print(psi)

    y0 = [phi, -1.5 * psi, -0.5 * K * conf_time * psi, -1.5 * psi, -0.5 * K * conf_time * psi]
    y0 = y0 + [-2 * psi, 2 * K * conf_time * psi / 3.0, 0.0] + [0.0 for i in range(n_g_moments-3)]
    y0 = y0 + [-2 * psi, 2 * K * conf_time * psi / 3.0, 2 * psi * (K * conf_time)**2 / 15.0] + [0.0 for i in range(n_nv_moments-3)]
    y0 = y0 + list(-0.25 * y0[5 + n_g_moments] * dlnf_dlnq) + list(-0.25 * y0[6 + n_g_moments] * dlnf_dlnq) + list(-0.25 * y0[7 + n_g_moments] * dlnf_dlnq)

    sol = solve_ivp(func, [a[0], a[-1]], y0, t_eval = a, args=(K, r_v0, r_m0, r_cdm0, r_b0, r_r0, r_g0, r_nv0, r_c0), method='RK45')
    y = sol.y

    Y0, Y1, Y2 = np.array([]), np.array([]), np.array([])
    for j in range(len(a)):
        E = (q**2 + (a[j]*m_v/T_nv0)**2)**0.5
        p_h = boole(q, q**2 * E * f0)
        P_h = boole(q, q**4 * f0 / E)/3.0

        temp = y[5+n_g_moments+n_nv_moments: 5+n_g_moments+n_nv_moments + len(q),j]
        h_term = ( boole(q, q**2 * E * f0 * temp.T) ) /(p_h)
        Y0 = np.append(Y0, h_term)

        temp = y[5+n_g_moments+n_nv_moments + len(q): 5+n_g_moments+n_nv_moments + 2*len(q),j]
        h_term = ( boole(q, q**3 * f0 * temp.T) ) /(p_h + P_h)
        Y1 = np.append(Y1, h_term)

        temp = y[5+n_g_moments+n_nv_moments + 2*len(q): 5+n_g_moments+n_nv_moments + 3*len(q),j]
        h_term = 2*( boole(q, q**4 * f0 * temp.T / E) ) / (3 * ( p_h + P_h ))
        Y2 = np.append(Y2, h_term)
    y = np.concatenate((y[ :5 + n_g_moments + n_nv_moments], Y0.reshape(1, len(Y0)), Y1.reshape(1, len(Y1)), Y2.reshape(1, len(Y2)) ), axis = 0)

    vec_expint = np.vectorize(expint)
    hori = vec_expint(a, r_v0, r_m0, r_r0, r_c0)
    f = interpolate.interp1d(hori, a)
    try:
        a_hori = f(1/K)
        return np.log10(a), y, a_hori
    except:
        return np.log10(a), y, False

def draw(x, y, visibility, a_eq, a_recomb, a_hori):
    ymax = 0
    ymin = 0
    for i in range(len(visibility)):
        if visibility[i]:
            Y = np.log10(np.abs(y[i]))
            index = Y>-3
            X, Y = x[index], Y[index]
            ax.plot(X, Y, label = labels[i])
            ymax = np.max(np.array([ymax, np.max(Y)]))
            ymin = np.min(np.array([ymin, np.min(Y)]))

    x_ticks = np.array(range(int(np.ceil(x[0])), int(np.floor(x[-1]))+1, 1))
    x_ticklabels = 10.0**x_ticks

    #matter-rad equality and recomb and horizon crossing
    ax.plot(np.log10(np.array([a_eq, a_eq])), [ymax, ymin], '--')
    ax.annotate('Radiation - matter equality', (np.log10(a_eq), ymax), textcoords = 'offset pixels', xytext = (5,-190), rotation = 90)
    ax.plot(np.log10(np.array([a_recomb, a_recomb])), [ymax, ymin], '--')
    ax.annotate('Last Scattering Surface', (np.log10(a_recomb), ymax), textcoords = 'offset pixels', xytext = (5,-170), rotation = 90)
    if a_hori!=False:
        ax.plot(np.log10(np.array([a_hori, a_hori])), [ymax, ymin], '--')
        ax.annotate('Horizon crossing', (np.log10(a_hori), ymax), textcoords = 'offset pixels', xytext = (5,-120), rotation = 90)

    ax.set(xlabel = 'a', ylabel = 'Perturbation',
            xticks = x_ticks, xticklabels = x_ticklabels,
            title = "k = "+str(np.round(k,7))+" h Mpc$^{-1}$",
            xlim = (slower.val, supper.val))
    ax.legend()
    ax.grid()

c = 2997.92458  #100 km s^-1
h = 0.6871
p_crit = 8.098e-11 * h**2   #eV^4 / (h_cross c)^3

#CDM
r_cdm0 = 0.255

#Baryons
r_b0 = 0.045

r_m0 = r_cdm0 + r_b0

#Photons
T0 = 2.34865e-4   #eV / Kb
r_g0 = np.pi**2 * T0**4 / (15 * p_crit)
n_g_moments = 3

#Massless Neutrinos
T_nv0 = (4/11.0)**(1/3.0) * T0
r_nv0 = 7 * np.pi**2 * T_nv0**4 / (40 * p_crit)
n_nv_moments = 5

r_r0 = r_g0 + r_nv0

#Massive Neutrinos
n_h_moments = 3
m_v = 0.1    #eV
q = np.linspace(0, 20, 21)
f0 = 1/(np.exp(q) + 1)
dlnf_dlnq = -q * np.exp(q) / (np.exp(q) + 1)
E = (q**2 + (m_v/T_nv0)**2)**0.5
r_h0 = T_nv0**4 * (boole(q, q**2 * E * f0)) / (np.pi**2 * p_crit)

r_c0 = 0.0#1 - r_v0 - r_m0 - r_r0

#DE
r_v0 = 1 + r_c0 - r_m0 - r_r0 - r_h0

print(r_cdm0, r_b0, r_g0, r_nv0, r_h0, r_v0)

n = 0.9660499 #spectral index of initial power spectrum
a_eq = r_r0/r_m0
a_recomb = 1/1101.0

k = 0.1
start, end = -8, -1

visibility = np.full((1+2+2+n_g_moments+n_nv_moments+n_h_moments), False)
visibility[[0,1,3,5,8,13]] = True
labels = np.array(['$\Phi$', '$\delta_{cdm}$', '$v_{cdm}$', '$\delta_{b}$', '$v_{b}$']
                    +['$\delta_{\\gamma,'+str(i)+'}$' for i in range(n_g_moments)]
                    +['$\delta_{\\nu,'+str(i)+'}$' for i in range(n_nv_moments)]
                    +['$\delta_{h}$', '$v_{h}$', '$\sigma_{h}$'])

fig, ax = pl.subplots(nrows=1, ncols=1)
pl.subplots_adjust( left = 0.05, bottom = 0.25, right = 0.85 )

axcolor = 'lightgoldenrodyellow'

axk = pl.axes([0.1, 0.12, 0.75, 0.03], facecolor=axcolor)
axlower = pl.axes([0.1, 0.075, 0.75, 0.03], facecolor=axcolor)
axupper = pl.axes([0.1, 0.02, 0.75, 0.03], facecolor=axcolor)
axcheck = pl.axes([0.88, 0.2, 0.1, 0.7], facecolor=axcolor, title = 'Variables')

sk = Slider(axk, 'k (h Mpc$^{-1}$, log scale)', -7, 2, valinit=np.log10(k))
slower = Slider(axlower, '$a_{min}$ (log scale)', -8, -2, valinit=start)
supper = Slider(axupper, '$a_{max}$ (log scale)', -8, 2, valinit=end)
check = CheckButtons(axcheck, labels, visibility)

x, y, a_hori = solve(c*k, r_v0, r_m0, r_cdm0, r_b0, r_r0, r_g0, r_nv0, r_c0, start, end)
#df = pd.DataFrame(data = {"a":x, "cdm overdensity":y[1+2+2+n_g_moments+n_nv_moments]})
#df.to_csv('k_'+str(k)+'_data.csv', index = False)
draw(x, y, visibility, a_eq, a_recomb, a_hori)

def k_update(val):
    global k, start, end, x, y, a_hori
    k = 10**sk.val

    ax.cla()

    x, y, a_hori = solve(c*k, r_v0, r_m0, r_cdm0, r_b0, r_r0, r_g0, r_nv0, r_c0, start, end)
    draw(x, y, visibility, a_eq, a_recomb, a_hori)

    fig.canvas.draw()

def lim_update(val):
    ax.set(xlim = (slower.val, supper.val))
    fig.canvas.draw()

def check_update(label):
    global visibility, k, start, end, x, y, a_hori
    ax.cla()
    visibility[labels == label] = np.logical_not(visibility[labels == label])
    draw(x, y, visibility, a_eq, a_recomb, a_hori)

    fig.canvas.draw()

sk.on_changed(k_update)
slower.on_changed(lim_update)
supper.on_changed(lim_update)
check.on_clicked(check_update)
pl.show()


"""Common transform operations."""
from pathlib import Path
from typing import Any, Dict, List, Optional

import pandas as pd
from joblib import Memory

from dbcp.constants import FIPS_CODE_VINTAGE
from dbcp.transform.geocoding import GoogleGeocoder
from pudl.helpers import add_fips_ids as _add_fips_ids

UNIX_EPOCH_ORIGIN = pd.Timestamp("01/01/1970")
# Excel parser is simplified and will be one day off for dates < 1900/03/01
# The origin is actually 12/31/1899, but because Excel mistakenly thinks
# 1900 was a leap year, I cancel out that error by setting the origin to 12/30/1899.
# See xlrd.xldate.py:xldate_as_datetime for complete implementation.
EXCEL_EPOCH_ORIGIN = pd.Timestamp("12/30/1899")

try:  # docker path
    # 3 directories above current module
    geocoder_local_cache = Path("/app/data/geocoder_cache")
    assert geocoder_local_cache.exists()
except AssertionError:  # local path
    # 4 directories above current module
    geocoder_local_cache = Path(__file__).resolve().parents[3] / "data/geocoder_cache"
    assert geocoder_local_cache.exists()
# cache needs to be accessed outside this module to call .clear()
# limit cache size to 100 KB, keeps most recently accessed first
GEOCODER_CACHE = Memory(location=geocoder_local_cache, bytes_limit=2**19)

def normalize_multicolumns_to_rows(
    df: pd.DataFrame,
    attribute_columns_dict: Dict[str, List[str]],
    index_cols: Optional[List[str]] = None,
    preserve_original_names=True,
    dropna=True,
) -> pd.DataFrame:
    """Convert a denormalized one-to-many relationship encoded as multiple columns to a row-based table.

    This is essentially the same as pd.melt() except that it handles multiple linked attributes
    such as in the example below. pd.melt() can only convert a single attribute.

    Args:
        df (pd.DataFrame): dataframe with multivalued column(s) encoded as multiple columns
        attribute_columns_dict (Dict[str,List[str]]): dict mapping new value names to a list of
        columns containing that value. If there are multiple such lists, the order of associated
        columns must be the same (eg. if numbered, sorted in same order). See example below.
        index_cols (Optional[List[str]], optional): Columns to use as IDs in original dataframe. If
        None, use existing index. Defaults to None.
        preserve_original_names (bool, optional): Sometimes multicolumn names contain information
        (such as a ranking). If True, keep one of the original column names to preserve this
        information. This assumes associated columns can be identified by a single member (like if
        they share a numbering schema, as in the example below). Defaults to True.
        dropna (bool, optional): Many multicolumns are sparse and produce many empty rows upon
        conversion to long format. If True, drop those rows. Defaults to True.

    Returns:
        pd.DataFrame: one-to-many table

    Example:
    original_df has columns [
        fuel_1, fuel_2, ... , fuel_N,
        capacity_of_fuel_1, capacity_of_fuel_2, ... , capacity_of_fuel_N
        ]
    output_df has columns [fuel, capacity] with N times as many rows.

    create output_df with:
    normalize_multicolumns_to_rows(
        original_df,
        attribute_columns_dict = {
            'fuel': ['fuel_1', 'fuel_2', 'fuel_N'],
            'capacity': ['capacity_of_fuel_1', 'capacity_of_fuel_2', 'capacity_of_fuel_N'],
            <more attribute:column mappings here if needed>,
        },
        preserve_original_names=False
    )
    Note: the lists of fuel_N and capacity_of_fuel_N must be in the same order, or the associations
    will be wrong (fuel_2 with capacity_of_fuel_1, for example). Really it should be a tabular data
    structure rather than multiple independent lists.
    """
    if index_cols is not None:
        df = df.set_index(index_cols)

    new_names = attribute_columns_dict.keys()
    column_groups = attribute_columns_dict.values()
    chunks = []
    for linked_columns in zip(*column_groups):  # Nth value of each list
        rename_dict = dict(zip(linked_columns, new_names))
        chunk = df.loc[:, list(linked_columns)].rename(columns=rename_dict)
        if preserve_original_names:
            # Assumes associated columns can be identified by a single member.
            # For example, (type_1, value_1), (type_2, value_2) share a numbering schema
            chunk["original_group"] = linked_columns[0]
        chunks.append(chunk)

    output: pd.DataFrame = pd.concat(chunks)
    if dropna:
        output.dropna(subset=list(new_names), how="all", inplace=True)

    return output.sort_index().reset_index()

def multiformat_string_date_parser(
    dates: pd.Series, numeric_origin=EXCEL_EPOCH_ORIGIN
) -> pd.Series:
    """Iteratively parse a column of date strings with heterogeneous formatting.

    The LBNL ISO Queue contains a variety of date formats. I couldn't get
    pd.to_datetime() to convert all of them with a single function call because
    it assumes that if the first N rows have the same format, all of them do.
    This solution iteratively calls pd.to_datetime() and inputs the results to .fillna()

    Args:
        date_strings (pd.Series): column of strings to be converted to pd.Timestamp
        parse_excel (bool, optional): Whether to try to interpret remaining strings as excel date offset integers. Defaults to True.

    Returns:
        pd.Series: dates converted to pd.Timestamp
    """
    if not pd.api.types.is_string_dtype(dates):
        raise ValueError(f"Column is not a string dtype. Given {dates.dtype}.")

    # Fill incomplete dates that contain only a year, eg "2020"
    # Conservatively only do this for 4 digit numbers from 1990-2039
    dates = dates.str.replace(
        r"^(199\d|20[0123]\d)$", lambda x: f"07/01/{x.group(1)}", regex=True
    )

    # separate numeric encodings from string encodings
    is_numeric_string = dates.str.isnumeric().fillna(False)
    date_strings = dates.loc[~is_numeric_string]

    # parse strings
    parsed_dates = pd.to_datetime(
        date_strings, infer_datetime_format=True, errors="coerce"
    )
    remaining_nan = parsed_dates.isna().sum()
    while remaining_nan > 0:
        nans = parsed_dates.isna()
        nan_to_dates = pd.to_datetime(
            date_strings[nans], infer_datetime_format=True, errors="coerce"
        )
        parsed_dates = parsed_dates.fillna(nan_to_dates)
        new_remaining_nan = nans.sum()
        if new_remaining_nan == remaining_nan:  # no improvement
            # try specific weird formats
            formats = [
                "%d%b%Y",  # 01Jan2020
                "%m/%y",  # 01/20 or 1/20
                "%b-%y",  # Jan-20
            ]
            for fmt in formats:
                nan_to_dates = pd.to_datetime(
                    date_strings[nans], format=fmt, errors="coerce"
                )
                parsed_dates = parsed_dates.fillna(nan_to_dates)
                nans = parsed_dates.isna()
                if nans.sum() == 0:
                    break
            break
        else:
            remaining_nan = new_remaining_nan

    # handle numeric encodings
    numbers = pd.to_numeric(dates.loc[is_numeric_string], errors="coerce")
    encoded_dates = numeric_offset_date_encoder(numbers, origin=numeric_origin)

    # recombine
    new_dates = pd.concat([parsed_dates, encoded_dates], copy=False).loc[dates.index]
    pd.testing.assert_index_equal(new_dates.index, dates.index)

    return new_dates

def numeric_offset_date_encoder(
    series: pd.Series,
    origin=EXCEL_EPOCH_ORIGIN,
    unit="d",
    roundoff: Optional[str] = None,
) -> pd.Series:
    """Convert column of numeric date offsets (like 45059) to pd.Timestamp.

    Warning: validation is left to the user! Check for unexpected dates.

    Args:
        series (pd.Series): numeric date offsets
        origin (pd.Timestamp): epoch origin, such as 1/1/1970 for unix
        unit (str, optional): pandas frequency string. Defaults to 'd'.
        roundoff (Optional[str], optional): pandas frequency string to round to, such as 'L' for milliseconds. Defaults to None.

    Raises:
        ValueError: if input column is not numeric

    Returns:
        pd.Series: output timestamps
    """
    if len(series) == 0:  # accept empty series
        return series
    if not pd.api.types.is_numeric_dtype(series):
        raise ValueError(f"Series is not numeric. Given {series.dtype}")
    offsets = pd.to_timedelta(series, unit=unit)
    date = offsets + origin
    if roundoff:
        # remove roundoff error to desired precision
        date = date.dt.round(freq=roundoff)
    return date

def parse_dates(series: pd.Series, expected_mean_year=2020) -> pd.Series:
    """Convert a column to pd.Datetime using one of several parsing strategies.

    Numeric columns are interpreted as either Unix or Excel datetime encoding.
    The choice of epoch is determined by which encoding produces dates closer
    to the expected_mean_year. 70 years separate Unix from Excel epochs, so
    this choice is usually clear.

    String columns are passed to the multiformat_string_date_parser.

    Args:
        series (pd.Series): encoded dates
        expected_mean_year (int, optional): year to compare outputs to when
            choosing between Unix and Excel epochs. For numeric columns only.
            Defaults to 2020.

    Returns:
        pd.Series: new column of pd.Datetime
    """
    if pd.api.types.is_numeric_dtype(series):
        unix_dates = numeric_offset_date_encoder(series, origin=UNIX_EPOCH_ORIGIN)
        excel_dates = numeric_offset_date_encoder(series, origin=EXCEL_EPOCH_ORIGIN)
        unix_diff = expected_mean_year - unix_dates.dt.year.mean()
        excel_diff = expected_mean_year - excel_dates.dt.year.mean()
        if abs(unix_diff) < abs(excel_diff):
            return unix_dates
        else:
            return excel_dates
    else:
        # assumes excel epoch when mixed with strings
        return multiformat_string_date_parser(series)

def _geocode_row(
    ser: pd.Series, client: GoogleGeocoder, state_col="state", locality_col="county"
) -> List[str]:
    """Function to pass into pandas df.apply() to geocode state/locality pairs.

    Args:
        ser (pd.Series): a row of a larger dataframe to geocode
        client (GoogleGeocoder): client for Google Maps Platform API
        state_col (str, optional): name of the column of state names. Defaults to 'state'.
        locality_col (str, optional): name of the column of locality names. Defaults to 'county'.

    Returns:
        List[str]: geocoded_locality_name, geocoded_locality_type, and geocoded_containing_county
    """
    client.geocode_request(name=ser[locality_col], state=ser[state_col])
    return client.describe()

@GEOCODER_CACHE.cache()
def _geocode_locality(
    state_locality_df: pd.DataFrame, state_col="state", locality_col="county"
) -> pd.DataFrame:
    """Use Google Maps Platform API to look up information about state/locality pairs in a dataframe.

    Args:
        state_locality_df (pd.DataFrame): dataframe with state and locality columns
        state_col (str, optional): name of the column of state names. Defaults to 'state'.
        locality_col (str, optional): name of the column of locality names. Defaults to 'county'.

    Returns:
        pd.DataFrame: new columns 'geocoded_locality_name', 'geocoded_locality_type', 'geocoded_containing_county'
    """
    # NOTE: the purpose of the cache decorator is primarily to
    # reduce API calls during development. A secondary benefit is to reduce
    # execution time due to slow synchronous requests.
    # That's why this is persisted to disk with joblib, not in memory with LRU_cache or something.
    # Because it is on disk, caching the higher level dataframe function causes less IO overhead
    # than caching individual API calls would.
    # Because the entire input dataframe must be identical to the cached version, I
    # recommend subsetting the dataframe to only state_col and locality_col when calling
    # this function. That allows other, unrelated columns to change but still use the geocode cache.
    geocoder = GoogleGeocoder()
    new_cols = state_locality_df.apply(
        _geocode_row,
        axis=1,
        result_type="expand",
        client=geocoder,
        state_col=state_col,
        locality_col=locality_col,
    )
    new_cols.columns = [
        "geocoded_locality_name",
        "geocoded_locality_type",
        "geocoded_containing_county",
    ]
    return new_cols

def add_county_fips_with_backup_geocoding(
    state_locality_df: pd.DataFrame, state_col="state", locality_col="county"
) -> pd.DataFrame:
    """Add state and county FIPS codes to a DataFrame with state and locality columns.

    This function is tolerant of mis-spellings and heterogeneous town/city/county types
    because it uses the Google Maps Platform API to re-process initial matching failures.

    Args:
        state_locality_df (pd.DataFrame): dataframe with state and locality columns
        state_col (str, optional): name of the column of state names. Defaults to 'state'.
        locality_col (str, optional): name of the column of locality names. Defaults to 'county'.

    Returns:
        pd.DataFrame: copy of state_locality_df with new columns 'geocoded_locality_name', 'geocoded_locality_type', 'geocoded_containing_county'
    """
    filled_state_locality = state_locality_df.loc[:, [state_col, locality_col]].fillna(
        ""
    )  # copy
    # first try a simple FIPS lookup and split by valid/invalid fips codes
    # The only purpose of this step is to save API calls on the easy ones (most of them)
    with_fips = _add_fips_ids(
        filled_state_locality,
        state_col=state_col,
        county_col=locality_col,
        vintage=FIPS_CODE_VINTAGE,
    )
    fips_is_nan = with_fips.loc[:, "county_id_fips"].isna()
    if not fips_is_nan.any():
        # standardize output columns
        with_fips["geocoded_locality_name"] = with_fips[locality_col]
        with_fips["geocoded_locality_type"] = "county"
        with_fips["geocoded_containing_county"] = with_fips[locality_col]
        return with_fips

    good_fips = with_fips.loc[~fips_is_nan, :].copy()
    # standardize output columns
    good_fips["geocoded_locality_name"] = good_fips[locality_col]
    good_fips["geocoded_locality_type"] = "county"
    good_fips["geocoded_containing_county"] = good_fips[locality_col]

    # geocode the lookup failures - they are often city/town names (instead of counties) or simply mis-spelled
    nan_fips = with_fips.loc[fips_is_nan, :].copy()
    geocoded = _geocode_locality(
        nan_fips.loc[:, [state_col, locality_col]],
        # pass subset to _geocode_locality to maximize chance of a cache hit
        # (this way other columns can change but caching still works)
        state_col=state_col,
        locality_col=locality_col,
    )
    nan_fips = pd.concat([nan_fips, geocoded], axis=1)
    # add fips using geocoded names
    filled_fips = _add_fips_ids(
        nan_fips,
        state_col=state_col,
        county_col="geocoded_containing_county",
        vintage=FIPS_CODE_VINTAGE,
    )

    # recombine and restore row order
    cols_to_keep = [
        "state_id_fips",
        "county_id_fips",
        "geocoded_locality_name",
        "geocoded_locality_type",
        "geocoded_containing_county",
    ]
    recombined = pd.concat([good_fips, filled_fips], axis=0).loc[
        state_locality_df.index, cols_to_keep
    ]

    # attach to original df
    out = pd.concat([state_locality_df, recombined], axis=1)

    return out

def replace_value_with_count_validation(
    df: pd.DataFrame,
    col: str,
    val_to_replace: Any,
    replacement: Any,
    expected_count: int,
) -> None:
    """Manually replace values, but with a minimal form of validation to guard against future changes.

    Args:
        df (pd.DataFrame): the source dataframe
        col (str): the name of the column containing values to replace
        val_to_replace (Any): value to replace
        replacement (Any): replacement value
        expected_count (int): known number of replacements to make

    Raises:
        ValueError: if expected count of replacements does not match observed count
    """
    matches = df.loc[:, col] == val_to_replace
    observed_count = matches.sum()
    if observed_count != expected_count:
        raise ValueError(
            f"Expected count ({expected_count}) of {val_to_replace} "
            f"does not match observed count ({observed_count})"
        )

    df.loc[matches, col] = replacement
    return

def bedford_addfips_fix(df, state_col="state", county_col="county") -> None:
    """Workaround to fix addfips data problem.

    addfips includes Bedford City, VA in its county database even though it was
    absorbed by Bedford County, VA in 2013. Its FIPS code no longer exists.
    """
    # workaround for addfips Bedford, VA problem
    is_va = df[state_col].str.lower().isin({"va", "virginia"})
    is_bedford = df[county_col].str.lower().str.startswith("bedford")
    df.loc[is_va & is_bedford, county_col] = "Bedford County"
    return


"""Containers module."""
from dependency_injector import containers, providers

from apps.handlers import (
    DemoHandler,
    MessageEventHandler,
    UserEventHandler,
)
from apps.libs.database.aio_redis import RedisPool

class Container(containers.DeclarativeContainer):
    """Container"""

    wiring_config = containers.WiringConfiguration(
        modules=[],
        packages=[
            "apps.handlers",
            "apps.routers"
        ],
    )

    # [database]
    redis_pool = providers.Resource(RedisPool)

    # handlers
    demo_handler = providers.Factory(DemoHandler)
    message_event_handler = providers.Factory(MessageEventHandler)
    user_event_handler = providers.Factory(
        UserEventHandler,
        redis=redis_pool
    )


import os

import pandas as pd
import tweepy
from tweepy import OAuthHandler

from extract.utils import execute_query, snowflake_engine_factory

def get_twitter_mentions():
    # Twitter credentials
    # Obtain them from your twitter developer account

    # Need to add this or another Twitter Developer API Key to SysVars - This is currently my personal API Key
    consumer_key = os.getenv("TWITTER_CONSUMER_KEY")
    consumer_secret = os.getenv("TWITTER_CONSUMER_SECRET")
    access_key = os.getenv("TWITTER_ACCESS_KEY")
    access_secret = os.getenv("TWITTER_ACCESS_SECRET")

    # Pass your twitter credentials to tweepy via its OAuthHandler
    auth = OAuthHandler(consumer_key, consumer_secret)
    auth.set_access_token(access_key, access_secret)
    api = tweepy.API(auth, wait_on_rate_limit=True, wait_on_rate_limit_notify=True)

    # Create database connection and cursor
    engine = snowflake_engine_factory(os.environ, "TRANSFORMER", "util")
    connection = engine.connect()

    # Create empty dataframe with required columns
    db_tweets = pd.DataFrame(
        columns=[
            "username",
            "text",
            "full_name",
            "user_url",
            "url",
            "retweet_text",
            "original_tweet_date",
            "retweeted_status",
            "retweet_count",
            "created_at",
            "location",
            "followers",
            "user_id",
            "favorite_count",
            "lang",
            "verified",
            "hashtags",
            "following_count",
            "is_tweet_reply",
            "id",
            "longitude_latitude",
        ]
    )

    # Fetch latest data from existing ANALYTICS.SOCIAL_MENTIONS.TWITTER relation
    query = """
    SELECT MAX(CREATED_AT - interval '1 day')::date::varchar AS DATE,
    MAX(CREATED_AT)::VARCHAR AS TIMESTAMP
    FROM analytics.social_mentions.twitter
    """

    try:
        results = execute_query(engine, query)
    except Exception as e:
        print(f"""Oh no! There was an error executing your query: {e}""")

    # Retrieve all tweets >= Max Created At in ANALYTICS.SOCIAL_MENTIONS.TWITTER relation
    tweets = tweepy.Cursor(api.search, q="mattermost", since=f"{results[0][0]}").items(5000)

    # Loop through new tweets and extract relevant fields to populate dataframe.
    for tweet in tweets:
        is_tweet_reply = tweet.in_reply_to_screen_name is not None
        username = tweet.user.screen_name
        full_name = tweet.user.name
        user_url = tweet.user.url
        url = f"https://twitter.com/{tweet.user.screen_name}/status/{tweet.id}"
        retweet_count = tweet.retweet_count
        verified = tweet.user.verified
        user_id = tweet.user.id
        favorite_count = tweet.favorite_count
        location = tweet.user.location
        following = tweet.user.friends_count
        followers = tweet.user.followers_count
        created_at = tweet.created_at.strftime("%Y-%m-%d %H:%M:%S")
        lang = tweet.lang
        hashtags = str(tweet.entities["hashtags"])
        longitude_latitude = tweet.coordinates
        tweet_id = tweet.id

        try:
            text = tweet.text
            retweet_text = tweet.retweeted_status.text
            original_tweet_date = tweet.retweeted_status.created_at.strftime("%Y-%m-%d %H:%M:%S")
            is_retweet = tweet.retweeted_status.text is not None
        except AttributeError:  # Not a Retweet
            text = tweet.text
            original_tweet_date = None
            retweet_text = None
            is_retweet = False

        # Add variables to tweet list to be inserted into dataframe:
        ith_tweet = [
            username,
            text,
            full_name,
            user_url,
            url,
            retweet_text,
            original_tweet_date,
            is_retweet,
            retweet_count,
            created_at,
            location,
            followers,
            user_id,
            favorite_count,
            lang,
            verified,
            hashtags,
            following,
            is_tweet_reply,
            tweet_id,
            longitude_latitude,
        ]
        # Append to dataframe - db_tweets
        db_tweets.loc[len(db_tweets)] = ith_tweet

    # Append dataframe to ANALYTICS.SOCIAL_MENTIONS.TWITTER relation
    db_tweets[db_tweets["created_at"] > results[0][1]].to_sql(
        "twitter",
        con=connection,
        index=False,
        schema="SOCIAL_MENTIONS",
        if_exists="append",
    )

if __name__ == "__main__":
    get_twitter_mentions()


from rest_framework.serializers import (
    HyperlinkedModelSerializer
)
from user.serializers import(
    CompanySerializer,
    BuyerSerializer,
    AddressSerializer,
)
from .models import Item, Order, OrderItem, Review

class ItemSerializer(HyperlinkedModelSerializer):

    company = CompanySerializer(read_only=True)

    class Meta:
        model = Item
        fields = "__all__"
        extra_kwargs = {
            "url": {
                "lookup_field": "slug",
                "view_name": "api-item-detail"
            }
        }

class OrderSerializer(HyperlinkedModelSerializer):

    user = BuyerSerializer(read_only=True)
    address = AddressSerializer(read_only=True)

    class Meta:
        model = Order
        fields = "__all__"
        extra_kwargs = {
            "url": {
                "lookup_field": "oid",
                "view_name": "api-order-detail"
            }
        }

class OrderItemSerializer(HyperlinkedModelSerializer):

    order = OrderSerializer(read_only=True)
    item = ItemSerializer(read_only=True)

    class Meta:
        model = OrderItem
        fields = "__all__"
        extra_kwargs = {
            "url": {
                "lookup_field": "slug",
                "view_name": "api-orderitem-detail"
            }
        }

class ReviewSerializer(HyperlinkedModelSerializer):

    item = ItemSerializer(read_only=True)
    order = OrderSerializer(read_only=True)

    class Meta:
        model = Review
        fields = "__all__"
        extra_kwargs = {
            "url": {
                "lookup_field": "slug",
                "view_name": "api-review-detail"
            }
        }


#!/usr/bin/env python3

import sys
import re
from itertools import combinations

if len(sys.argv) != 2:
    print("Usage: {} <questions.kqb>".format(sys.argv[0]))
    sys.exit(1)

def read_questions(filename):
    questions = []
    with open(filename, 'r') as f:
        for line in f.readlines():
            line = line.rstrip()

            if line.startswith('#'):
                continue

            rex = re.compile("^(?P<question>\w+)\(.*\)$")
            s = rex.search(line)
            if s:
                questions.append(s.group('question'))

    return questions

questions = read_questions(sys.argv[1])

# Next rules generation for typhoid fever diagnostic
# Following the rules if has from 5-9 is cold

index = 1
for c in combinations(questions, 5):
    print("have_patient_typhoid_fever_{}".format(index))
    print("    use have_patient_typhoid_fever()")
    print("    when")
    for q in c:
        print("        questions.{}(True)".format(q))
    print("")
    index = index + 1


from django.urls import include, re_path
from rest_framework import routers

from . import views

router = routers.DefaultRouter()

router.register(r'risk_type', views.RiskTypeViewSet)
router.register(r'field', views.FieldViewSet)
router.register(r'field_type', views.FieldTypeViewSet)

urlpatterns = [
    re_path(r'^', include((router.urls))),
]


import cv2 as cv
from cv2 import resize
from cv2 import FONT_HERSHEY_COMPLEX_SMALL as Fuente
import numpy as np

ima = cv.imread('estrella3.png')
img = resize(ima, dsize = (500,500), interpolation = cv.INTER_CUBIC)
gris = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
img = cv.erode(gris, None, iterations=1)
img = cv.Canny(image=img, threshold1=127, threshold2=100)
img = cv.blur(img, ksize=(5,5))

contor,_ = cv.findContours(img, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)

for i in contor:
	e = 0.01*cv.arcLength(i,True)
	aprx = cv.approxPolyDP(i,e,True)
	x,y,w,h = cv.boundingRect(aprx)

if len(aprx) == 4:

    cv.putText(img, text='Es un cuadrado', org=(20,40), fontFace = Fuente, fontScale=1, color=(255,255,255), thickness=4, lineType=cv.LINE_8)

if len(aprx) == 10:

    cv.putText(img, text='Es una estrella', org=(20,40), fontFace = Fuente, fontScale=1, color=(255,255,255), thickness=4, lineType=cv.LINE_8)

if len(aprx) > 10:

    cv.putText(img, text='Es un circulo', org=(20,40), fontFace = Fuente, fontScale=1, color=(255,255,255), thickness=4, lineType=cv.LINE_8)

cv.imshow('ima',img)
cv.waitKey(0)


#!/home/saeed/Programs/miniconda3/bin/python

from numpy import min, max, pi
from datetime import datetime as dt

#__________________FUNCTION TO SCALE INPUT DATA FROM RANGE (a,b) TO NEW RANGE (c,d).

def scale_data(data, new_range=[4.,8.]):

    old_min = float(min(data))
    old_max = float(max(data))
    new_min = float(min(new_range))
    new_max = float(max(new_range))

    return ( (data - old_min) / (old_max - old_min) ) * (new_max - new_min) + new_min

##Example
##from numpy import arange
##print scale_data(arange(1.,256.), new_range=[4.,8.])

#__________________FUNCTIONS TO CONVERT KILOMETER TO DEGREE AND VIC VERCA.

def k2d(kilometer, radius=6371):

    return kilometer / (2.0 * radius * pi / 360.0)

def d2k(degrees, radius=6371):

    return degrees * (2.0 * radius * pi / 360.0)

#__________________FUNCTION TO GET CORRECT ORIGIN TIME.

def get_ot(yy,mm,dd,HH,MM,SS,MS):

    if SS >= 60:

        SS = SS - 60
        MM+=1

    if MM >= 60:

        MM = MM - 60
        HH+=1

    if HH >= 24:

        HH = HH - 24
        dd+=1

    if 0<=yy<=80: yy+=2000
    if 80<=yy<=99: yy+=1900

    ot = dt(yy,mm,dd,HH,MM,int(SS),MS)

    return ot


#! /bin/python

import re
import sys
import argparse
import requests
from urllib.parse import unquote
from clint.textui import progress
from bs4 import BeautifulSoup

def determinePage(url):
    if url.find('@') >= 0: # Test for a userpage
        print('Found userpage: ' + url)
        userpage(url)
    else:
        getpage= requests.get(url, headers={'user-agent': 'archive-dl'}, stream=True)
        getpage_soup=BeautifulSoup(getpage.text, 'html.parser')
        items= getpage_soup.find('div', {'id':'theatre-ia-wrap'}) # Test for a videopage
        if items is not None:
            sronly= getpage_soup.find('div', {'class':'streamo'}) # Test for stream only
            if sronly is not None:
                print('Found Stream Only page: ' + url)
                streamonlypage(url)
            else:
                print('Found video page: ' + url)
                videopage(url)
        elif getpage_soup.find('div', {'class':'download-directory-listing'}) is not None:
            print('Found download page: ' + url)
            downloadpage(url)
        else:
            print('Maybe a collection? ' + url)
            userpage(url) # If it's not a userpage or videopage it's probably a collection

def downloadFile(name, dl_url):
    r=requests.get(dl_url, headers={'user-agent': 'archive-dl'}, stream=True)
    if r.status_code == 200:
        with open(name, 'wb') as f:
            print("Downloading %s" % name)
            total_length = int(r.headers.get('content-length'))
            for chunk in progress.bar(r.iter_content(chunk_size=1024), expected_size=(total_length/1024) + 1):
                if chunk:
                    f.write(chunk)
                    f.flush()
        f.close()
    else:
        print('File not found.')

def downloadpage(url):
    getpage = requests.get(url, headers={'user-agent': 'archive-dl'}, stream=True)
    getpage_soup = BeautifulSoup(getpage.text, 'html.parser')
    download = getpage_soup.findAll('a')
    if verbose is True:
        print('Looking in ' + url)
    findFiles(url, download) # Find files on the download page
    findDirectories(url, download) # Look for directories

def findDirectories(url, download):
    loop = True # Create a loop to search through directories if they are found
    while loop is True:
        linksFound = False
        for links in download: # Looking for directories
            folder = links.get('href')
            if folder is not None and folder.endswith('/') and folder.endswith('create/') <= 0 and folder.endswith('web/') <= 0 and folder.endswith('about/') <= 0 and folder.endswith('.org/') <= 0 and folder.endswith('projects/') <= 0 and folder.endswith('donate/') <= 0 and folder.endswith('../') <= 0:
                upurl = url + folder
                if verbose is True:
                    print('Looking in ' + upurl)
                getpage = requests.get(upurl, headers={'user-agent': 'archive-dl'}, stream=True)
                getpage_soup = BeautifulSoup(getpage.text, 'html.parser')
                updownload = getpage_soup.findAll('a')
                findFiles(upurl, updownload) # Find files in the directory
                linksFound = True # Continue the loop if more links are found
        if linksFound is not False:
            loop = True
            download = updownload
            url = upurl
        else:
            loop = False

def findFiles(url, download):
    foundlist = []
    for i in download:
        dl= i.get('href')
        if dl is not None and verbose is True:
            print('Looking at ' + dl)
        if dl is not None and yesAudio is False and dl.find('.ia.mp4') >= 0:
            dl_url= url + dl
            name=unquote(dl.rsplit('/', 1)[-1]) # Grab the name from the url
            foundlist.append(dl.rsplit('.ia',1)[0]) # .ia.mp4 files to compare against later
            if yesDownload == True:
                fileExists(name, dl_url)
            elif justList == True:
                print('Found ' + name)
            else:
                writeFile(dl_url)
    for i in download:
        dl = i.get('href')
        if dl is not None and yesAudio is False and dl.find('.mp4') >= 0:
            dl_url= url + dl
            name=unquote(dl.rsplit('/', 1)[-1]) # Grab the name from the url
            test=dl.rsplit('.mp4',1)[0] # Test to see if file has already been grabbed
            if test not in foundlist:
                foundlist.append(test)
                if yesDownload == True:
                    fileExists(name, dl_url)
                elif justList == True:
                    print('Found ' + name)
                else:
                    writeFile(dl_url)
    for i in download:
        dl = i.get('href')
        if dl is not None and yesAudio is False and dl.find('.mkv') >= 0:
            dl_url= url + dl
            name=unquote(dl.rsplit('/', 1)[-1]) # Grab the name from the url
            test=dl.rsplit('.mkv',1)[0] # Test to see if file has already been grabbed
            if test not in foundlist:
                foundlist.append(test)
                if yesDownload == True:
                    fileExists(name, dl_url)
                elif justList == True:
                    print('Found ' + name)
                else:
                    writeFile(dl_url)
    for i in download:
        dl = i.get('href')
        if dl is not None and yesAudio is False and dl.find('.m.k.v') >= 0:
            dl_url= url + dl
            name=unquote(dl.rsplit('/', 1)[-1]) # Grab the name from the url
            test=dl.rsplit('.m.k.v',1)[0] # Test to see if file has already been grabbed
            if test not in foundlist:
                foundlist.append(test)
                if yesDownload == True:
                    fileExists(name, dl_url)
                elif justList == True:
                    print('Found ' + name)
                else:
                    writeFile(dl_url)
    for i in download:
        dl = i.get('href')
        if dl is not None and yesAudio is False and dl.find('.avi') >= 0:
            dl_url= url + dl
            name=unquote(dl.rsplit('/', 1)[-1]) # Grab the name from the url
            test=dl.rsplit('.avi',1)[0] # Test to see if file has already been grabbed
            if test not in foundlist:
                foundlist.append(test)
                if yesDownload == True:
                    fileExists(name, dl_url)
                elif justList == True:
                    print('Found ' + name)
                else:
                    writeFile(dl_url)
    for i in download:
        dl = i.get('href')
        if dl is not None and yesAudio is True and dl.find('.mp3') >= 0:
            dl_url= url + dl
            name=unquote(dl.rsplit('/', 1)[-1]) # Grab the name from the url
            test=dl.rsplit('.mp3',1)[0] # Test to see if file has already been grabbed
            if test not in foundlist:
                foundlist.append(test)
                if yesDownload == True:
                    fileExists(name, dl_url)
                elif justList == True:
                    print('Found ' + name)
                else:
                    writeFile(dl_url)
    for i in download:
        dl = i.get('href')
        if dl is not None and dl.find('.zip') >= 0:
            dl_url= url + dl
            name=unquote(dl.rsplit('/', 1)[-1]) # Grab the name from the url
            test=dl.rsplit('.zip',1)[0] # Test to see if file has already been grabbed
            if test not in foundlist:
                foundlist.append(test)
                if yesDownload == True:
                    fileExists(name, dl_url)
                elif justList == True:
                    print('Found ' + name)
                else:
                    writeFile(dl_url)

def fileExists(name, dl_url):
    try:
        file = open(name)
        file.close()
        return

        yes = set(['yes','y'])
        no = set(['no','n',''])

        while True:
            ans=input("File " + name + " already exists. Overwrite? N/y ").lower()
            if ans in yes:
                downloadFile(name, dl_url)
            elif ans in no:
                return
            else:
                print('Yes or No?')
    except FileNotFoundError:
        downloadFile(name, dl_url)

def getArguments():
    global url, textFile, yesDownload, justList, yesAudio, batchFile, verbose

    parser  =   argparse.ArgumentParser(description='Downloads media from Archive.org')
    group   =   parser.add_mutually_exclusive_group()

    group.add_argument('-w',
                        action='store',
                        dest='textFile',
                        default='video-dl.txt',
                        help='Create a list of the videos found with this name.',
                        required=False
                        )
    group.add_argument('-d',
                        action='store_true',
                        dest='yesDownload',
                        default=False,
                        help='Downloads videos',
                        required=False
                        )
    group.add_argument('-l',
                       action='store_true',
                       dest='justList',
                       default=False,
                       help='Just list the found videos.',
                       required=False
                       )
    parser.add_argument('-v',
                        action='store_true',
                        dest='verbose',
                        default=False,
                        help='Print detail info.',
                        required=False
                        )
    parser.add_argument('--audio',
                        action='store_true',
                        dest='yesAudio',
                        default=False,
                        help='Find audio files',
                        required=False
                        )

    parser.add_argument('--batch',
                        action='store_true',
                        dest='batchFile',
                        default=False,
                        help='Run using a batch of urls.',
                        required=False
                        )

    parser.add_argument('URL',
                        type=str,
                        help='<Required> url link'
                        )

    results     =   parser.parse_args()
    url         =   results.URL.strip().rstrip('/') + '/'
    textFile    =   results.textFile.strip()
    yesDownload =   results.yesDownload
    yesAudio    =   results.yesAudio
    justList    =   results.justList
    batchFile   =   results.batchFile
    verbose     =   results.verbose
    return

def main():
    getArguments()
    if batchFile is True:
        with open(url.rstrip('/'), "r") as f:
            for content in f.readlines():
                print('Finding files for ' + content.strip())
                determinePage(content.strip())
                print('\n')
    else:
        determinePage(url)

def streamonlypage(url):
    parseurl = url.split('/')
    game= parseurl[4]
    gameparsed= game.split('_')
    platform= gameparsed[0]
    xmlurl = 'https://archive.org/download/'+game+'/'+game+'_files.xml'
    findfile= requests.get(xmlurl, headers={'user-agent': 'archive-dl'}, stream=False)
    if findfile.status_code != 200:
        print('Unable to find file data')
        return
    findfile_soup= BeautifulSoup(findfile.text, 'xml')
    findfile_file= findfile_soup.find('file', {'name': re.compile(r'3ds$|a78$|bin$|chd$|cso$|gba$|gb$|gbc$|iso$|64$|nes$|sfc$|wad$|wbfs$|zip$')})
    if findfile_file == None:
        print('No file available for download')
        return

    filename = (findfile_file.get('name'))

    url = 'https://archive.org/download/'+game+'/'+filename

    if yesDownload == True:
        fileExists(filename, url)
    elif justList == True:
        print('Found ' + url)
    else:
        writeFile(url)

def userpage(url):
    getpage=requests.get(url, headers={'user-agent': 'archive-dl'}, stream=True)
    getpage_soup=BeautifulSoup(getpage.text, 'html.parser')
    items= getpage_soup.findAll('div', {'class':'item-ttl C C2'})
    for shows in items:
        show= shows.find('a').get('href')
        url= 'https://archive.org'+show+'/'
        videopage(url)

def videopage(url):
    parseurl = url.split('/')
    folderurl = '/'.join(parseurl[4:]).rstrip('/')
    url = 'https://archive.org/download/'+folderurl+'/'
    if verbose is True:
        print('Looking in ' + url)
    downloadpage(url)

def writeFile(dl_url):
    print('Writing ' + dl_url + ' to ' + textFile)
    f = open(textFile, "a")
    f.write(dl_url)
    f.write("\n")
    f.close()

if __name__ == "__main__":
    main()


from selenium import webdriver
from time import sleep
import pandas as pd
pd.set_option('display.max_columns', None)    # 显示所有列
pd.set_option('display.max_rows', None)
driver=webdriver.Chrome(executable_path="D:/Users/Anaconda3/chromedriver.exe")
driver.get("https://www.jd.com")
#找到搜索框
driver.find_element_by_xpath("//*[@id='key']").send_keys("编程书籍")
#点击按钮
driver.find_element_by_xpath("//*[@id='search']/div/div[2]/button/i").click()

driver.implicitly_wait(5)#休息5s
hrefs=[]
names=[]
prices=[]
commits=[]
shops=[]
for j in range(1,2):
    for i in range(1, 11):
        href = driver.find_element_by_xpath(
            "//ul[@class='gl-warp clearfix']/li[" + str(i) + "]//div[@class='p-img']/a").get_attribute("href")
        price = driver.find_element_by_xpath(
            "//ul[@class='gl-warp clearfix']/li[" + str(i) + "]//div[@class='p-price']")
        name = driver.find_element_by_xpath(
            "//ul[@class='gl-warp clearfix']/li[" + str(i) + "]//div[@class='p-name']//em")   #//*[@id="J_goodsList"]/ul/li[4]/div/div[3]/a/em
        commit = driver.find_element_by_xpath(
            "//ul[@class='gl-warp clearfix']/li[" + str(i) + "]//div[@class='p-commit']/strong")
        shop = driver.find_element_by_xpath(
            "//ul[@class='gl-warp clearfix']/li[" + str(i) + "]//div[@class='p-shopnum']")
        hrefs.append(href)
        names.append(name.get_attribute("textContent").replace('\n', '').replace('\t', ''))#.replace('\n', '').replace('\t', '')
        prices.append(price.get_attribute("textContent").replace('\n', '').replace('\t', '').replace('￥',''))
        commits.append(commit.get_attribute("textContent").replace('\n', '').replace('\t', ''))
        shops.append(shop.get_attribute("textContent").replace('\n', '').replace('\t', ''))
    driver.find_element_by_xpath("//*[@id='J_bottomPage']/span[1]/a[9]").click()
    sleep(2)
    print("第"+str(j)+"页")
print("爬取完毕！")
#存数据

import pandas as pd
D = {"链接": hrefs,
     "名称": names,
     "价格": prices,
     "评价": commits,
     "商店": shops}
data = pd.DataFrame(dict([(k, pd.Series(v)) for k, v in D.items()]))
print(data)
# 生成CSV文件
filename = "编程书籍-19.csv"
data.to_csv(filename, index=False,encoding='utf-8-sig')
print("关闭浏览器，保存数据")