# from ete3 import Tree
import logging, sys
from os import path
import re, csv, os
from time import clock
# import visualisation_taxonomic_tree_cluster as view
import numpy as np
# PythonDecorators/decorator_without_arguments.py
# class timer(object):
#
#     def __init__(self, f):
#         """
#         If there are no decorator arguments, the function
#         to be decorated is passed to the constructor.
#         """
#         self.f = f
#
#     def __call__(self, *args, **kwargs):
#         """
#         The __call__ method is not called until the
#         decorated function is called.
#         """
#         START_TIME = clock()
#         result = self.f(*args, **kwargs)
#         # print(self.f.__name__, 'run time:%6.2f'%(clock()-START_TIME), 'seconds.');
#         # print('-'*20)
#         return result
# @timer

def getAnnotatedProteinTaxId(stat_protein_file):
    annotated_genomes = {}
    with open(stat_protein_file) as csvfile:
        reader = csv.DictReader(csvfile, delimiter='\t')
        for row in reader:
            # print(row)
            if row['polyprotein_outline'] == "True":
                annotated_genomes.setdefault(row['taxon_id'], []).append(row['protein_id'])
                # print(row['taxon_id'], row['protein_id'])
    return annotated_genomes

def constructTaxonTree(taxonomy_file, unclassified_term_list):

    node_dict = {} #name:[child1, child2....]
    count_leaves = {} #"node name":int(number of leaves)
    leaves_taxonomy = {} # genome_id:string taxonomy
    genome_id_to_name = {}
    #438782	Abaca bunchy top virus	Viruses;ssDNA viruses;Nanoviridae;Babuvirus	1	/mirr[...]gbff.gz
    with open(taxonomy_file, 'r') as taxon_handle:
        for l in taxon_handle:
            genome_id, genome_name, taxonomy, genetic_code, gbff_file = l.split("\t")

            taxonomy = taxonomy.split(';')

            genome_id_to_name[genome_id] = genome_name
            #We don't want to count the unclassified taxonomy node
            #For example the virus "Tai Forest alphavirus" has the folowing taxonomy : Viruses;[...];Alphavirus;unclassified Alphavirus
            # we remove the last node and associate it with the name of the virus in the dictionnary genome_id_to_name
            unclassified_node_name_to_merge = []
            taxon_to_remove = []

            for i, taxon in enumerate(taxonomy):
                if any([taxon.startswith(unclassified_term) for unclassified_term in unclassified_term_list]):
                    taxon_to_remove.append(taxon)
                    unclassified_node_name_to_merge.append(taxon)
                    continue
                elif unclassified_node_name_to_merge:
                    f = True
                    taxonomy[i] = '{} ({})'.format(taxon, ';'.join(unclassified_node_name_to_merge) )
                    unclassified_node_name_to_merge = []
            if unclassified_node_name_to_merge:
                genome_id_to_name[genome_id] = "{} ({})".format(genome_name, ';'.join(unclassified_node_name_to_merge))
            [taxonomy.remove(t) for t in taxon_to_remove]

            leaves_taxonomy[genome_id] = tuple(taxonomy)
            root = taxonomy[0]
            node_dict.setdefault(root, set()) # add the root to the dict if not there

            updateTree(taxonomy,node_dict, count_leaves, genome_id)

    return node_dict, count_leaves, leaves_taxonomy, genome_id_to_name

def updateTree(taxonomy, node_dict, count_leaves, child):
    try:
        last_taxon = taxonomy.pop()
    except IndexError:
        return

    #update the node leaf count. He has one more leaf
    count_leaves[last_taxon] = count_leaves.setdefault(last_taxon, 0) + 1

    #add child to the last taxon if it is not already there
    node_dict.setdefault(last_taxon, set()).add(child)

    updateTree(taxonomy, node_dict, count_leaves, last_taxon)

def clusterFileParser(cluster_file, annotated_genomes):
    # 1093958|YP_004901696.1|362.0|None	759390|YP_003620408.1|362.0|None	2021667|YP_009408633.1|357.0|None
    with open(cluster_file, 'r') as cluster_handle:
        for i, cluster_line in enumerate(cluster_handle):
            cluster_elements = cluster_line.rstrip().split("\t")
            cluster_info = {'genome_ids':[], 'nb_polyprotein':0, 'nb_protein':len(cluster_elements), "cluster_id":i}
            cluster_info['polyproteins'] = []

            for element in cluster_elements:
                # print(element)
                #temporary solution should be change at some point
                try:
                    genome_id, protein_id, length, peptide_annotation = element.split('|')
                except ValueError: # Peptide info is not anymore in fasta header. use the dict annotated_genomes to identify annotated protein
                    genome_id, protein_id, length = element.split('|')
                if genome_id in annotated_genomes and protein_id in annotated_genomes[genome_id]:
                    peptide_annotation = "Peptide"
                else:
                    peptide_annotation = "None"
                if peptide_annotation == 'Peptide':
                    cluster_info['nb_polyprotein'] += 1
                    cluster_info['polyproteins'].append(genome_id)
                cluster_info['genome_ids'].append(element.split('|')[0])
            # genome_ids = {element.split('|')[0] for element in cluster}
            yield cluster_info

def getSharedTaxonomy(taxonomy_set):

    # valid_branches = set()
    taxonomy_iter = iter(taxonomy_set)
    current_shared_taxonomy = next(taxonomy_iter)

    for new_taxonomy in taxonomy_iter:
        if  current_shared_taxonomy[-1] in new_taxonomy:
            if new_taxonomy.index(current_shared_taxonomy[-1]) != current_shared_taxonomy.index(current_shared_taxonomy[-1]):
                logging.warning("The same node name found with different index in taxonomies:"+str(current_shared_taxonomy)+'_and_'+str(new_taxonomy))
            else:
                continue

        current_taxonomy_iter = iter(current_shared_taxonomy)
        #We want to get the last smallest share taxonomy between the two taxonomy
        # we take the smallest one as itererator and we loop on the other one
        smallest_taxonomy_iter, biggest_taxonomy_iter = (iter(new_taxonomy), iter(current_shared_taxonomy)) if len(new_taxonomy) <= len(current_shared_taxonomy) else (iter(current_shared_taxonomy), iter(new_taxonomy))

        for i, taxon in enumerate(biggest_taxonomy_iter):
            # print("taxon tested to be in current_shared_taxonomy ",taxon )
            if taxon != next(smallest_taxonomy_iter, False):
                # print(taxon, 'not in current ..')
                current_shared_taxonomy = new_taxonomy[:i]
                # print("new shared tax is set", ";".join(current_shared_taxonomy))
                break
    # last_common_node = current_shared_taxonomy[-1]
    # # print(last_common_node)
    # # print(current_shared_taxonomy)
    # for tax in taxonomy_set:
    #     i_last = tax.index(last_common_node)
    #     try:
    #         valid_branches.add(tax[i_last+1])
    #     except:
    #         valid_branches = {last_common_node}
    #         break

    # print("LAST TAX")
    # print(current_shared_taxonomy)
    # print('valid branch', valid_branches )
    # # print('NB GENOmE in cluster', len(cluster))
    # # print('NB GENOmE in subtree', sum( [count_leaves[t] for t in valid_branches]))
    # print("--------------")
    # subtree_nb_leaves =  sum( [count_leaves[t] for t in valid_branches])

    return current_shared_taxonomy #, valid_branches

def getValidBranches(last_common_node, current_shared_taxonomy, taxonomy_set):

    valid_branches = set()
    for tax in taxonomy_set:
        if last_common_node in tax:
            i_last = tax.index(last_common_node)

            if len(tax) > i_last+1:
                valid_branches.add(tax[i_last+1])

    if not valid_branches: # there is no better defined node in the cluster than the last_common_node
        return {last_common_node} # last common node is the node use to compute homogeneity

    if len(valid_branches) > 1:
        return valid_branches

    if len(valid_branches) == 1:
        # only one branch is in valid_branches which mean some genome are attached to last_common_node and all the other
        # ones have the better defined node of valid branch. which means we find potential valid branches in the valid ranch node
        last_common_node_from_valid_branch = valid_branches.pop()

        return getValidBranches(last_common_node_from_valid_branch, current_shared_taxonomy, taxonomy_set)

def getAlternativeTaxonId(alternative_taxon_id_file):
    alternative_taxon_id={} # key: taxon id value : set of alternative taxon id
    with open(alternative_taxon_id_file, 'r') as handle:
        for l in handle:
            #429564;77811	/mirror/ncbi/current/g...
            set_taxon_id = set(l.split("\t")[0].split(';'))
            for i, taxon in enumerate(set_taxon_id):
                alternative_taxon_id[taxon] = set_taxon_id - {taxon}
    return alternative_taxon_id

def get_filtered_taxonomy_set(genomes_ids, unclassified_term_list, leaves_taxonomy, alternative_taxon_id):
    '''
    Return set of different taxonomy found in the cluster

    '''
    taxonomy_set = set()
    unclassified_taxonomy_set = set()
    unclassified_genome = 0
    # unfound_genome = 0
    for i, taxon_id in enumerate(genomes_ids):
        taxonomy = None
        try:
            taxonomy = leaves_taxonomy[taxon_id]
        except:
            if taxon_id in alternative_taxon_id:
                for alternative in alternative_taxon_id[taxon_id]:
                    if alternative in leaves_taxonomy:
                        taxonomy = leaves_taxonomy[alternative]
                        genomes_ids[i] = alternative

            if not taxonomy:

                logging.warning("taxon id not found in taxonomy file or in alternative taxon id file: "+taxon_id)
                raise NameError("taxon id not found in taxonomy file or in alternative taxon id file: ",taxon_id)
                # unfound_genome += 1
                continue
        # Check if the first node after the root is an unclassified node
        # if it is this genome is not take into account in the homogeneity
        # print(taxon_id, taxonomy)
        # for unclassified_term in unclassified_term_list:
        #     if taxonomy[1].startswith(unclassified_term):
        #         unclassified_genome += 1
        #         unclassified_taxonomy_set.add(taxonomy)
        taxonomy_set.add(taxonomy)

    return taxonomy_set, unclassified_genome, unclassified_taxonomy_set

def initiateOuputFile(file_output):

    handle_stat = open(file_output, "w")
    #segment.taxon_id, cds.protein_id, has_peptide, len(cds.peptides), len(cds.cleavage_sites), is_sub_protein
    header = ['cluster_id',
                    'nb_of_protein',
                    'number_of_genome',
                    'number_of_genome_in_valid_branches',
                    'unclassified_genome',
                    'nb_polyprotein',
                    "number_leaves_in_subtree",
                    "homogeneity",
                    "shared_taxonomy",
                    'valid_branches',
                    'inflation',
                    'coverage',
                    "evalue",
                    "unclassified_cluster",
                    "first_node"]

    csv_writer = csv.DictWriter(handle_stat, fieldnames=header, delimiter='\t')
    csv_writer.writeheader()

    return csv_writer

def computeHomogeneity(cluster_info, unclassified_term_list, leaves_taxonomy, alternative_taxon_id, count_leaves):

    taxonomy_set, unclassified_genome,  unclassified_taxonomy_set= get_filtered_taxonomy_set(cluster_info['genome_ids'], unclassified_term_list, leaves_taxonomy,alternative_taxon_id)

    if unclassified_genome == len(cluster_info['genome_ids']): # the all proteins of the cluster are coming from unclassified genomes
        # assert  all([True for t in taxonomy_set if t[1] in unclassified_term_list]), "Some taxonomy are not unclassified while there are predicted as"
        unclassified_cluster = True
        effective_taxonomy_set = taxonomy_set
        assert taxonomy_set == unclassified_taxonomy_set
    else:
        unclassified_cluster = False
        effective_taxonomy_set = taxonomy_set - unclassified_taxonomy_set
    current_shared_taxonomy = getSharedTaxonomy(effective_taxonomy_set)
    valid_branches = getValidBranches(current_shared_taxonomy[-1], current_shared_taxonomy, effective_taxonomy_set)
    # input()
    ##Get nb genome from the cluster that are included in the valid branches
    nb_genome_in_valid_branch = 0
    nb_genome = len(set(cluster_info['genome_ids']))
    for genome_id in set(cluster_info['genome_ids']):
        if any([True for node in leaves_taxonomy[genome_id] if node in valid_branches]):
            nb_genome_in_valid_branch += 1

    subtree_nb_leaves = sum([count_leaves[t] for t in valid_branches])
    homogeneity = (nb_genome_in_valid_branch)/subtree_nb_leaves
    # print(nb_genome,"/",subtree_nb_leaves)
    try:
        first_node = current_shared_taxonomy[1]
    except IndexError: # the current shared tax is only Viruses
        first_node = current_shared_taxonomy[0]
    if unclassified_cluster:
        first_node = 'unclassified'

    current_shared_taxonomy = ';'.join(current_shared_taxonomy)
    valid_branches = '|'.join(valid_branches)

    assert (0 < homogeneity <= 1),"homogeneity value is over 1: %f"  % homogeneity
    # singleton = False if
    dict_info = {'cluster_id':cluster_info['cluster_id'],
                    'nb_of_protein':cluster_info['nb_protein'],
                    'number_of_genome_in_valid_branches':nb_genome_in_valid_branch,
                    'number_of_genome':nb_genome,
                    'unclassified_genome':unclassified_genome,
                    'nb_polyprotein':cluster_info['nb_polyprotein'],
                    "number_leaves_in_subtree":subtree_nb_leaves,
                    "homogeneity":homogeneity,
                    "shared_taxonomy":current_shared_taxonomy,
                    'valid_branches':valid_branches,
                    "unclassified_cluster":unclassified_cluster,
                    "first_node":first_node
                    }
    return dict_info

# @timer
def writeHomogeneity(iter_clusters, inflation,evalue, coverage, unclassified_term_list, leaves_taxonomy, alternative_taxon_id, count_leaves, genome_id_to_name, node_dict):
    homogeneities = []
    homogeneities_poly = []
    homogeneities_no_poly = []
    homogeneities_dsDNA = []
    homogeneities_no_dsDNA = []

    for i, cluster_info in enumerate(iter_clusters):
        #cat data/taxonomy/taxonomy_virus.txt | cut -f3 | cut -d';' -f2 | sort | uniq | more
        dict_info = computeHomogeneity(cluster_info, unclassified_term_list, leaves_taxonomy, alternative_taxon_id, count_leaves)

        dict_info["inflation"] = inflation
        dict_info["coverage"] = coverage
        dict_info["evalue"] = evalue

        if csv_writer:
            csv_writer.writerow(dict_info)
            # continue
        if not dict_info['unclassified_cluster']:
            homogeneities.append(dict_info['homogeneity'])

            if dict_info["nb_polyprotein"] == 0:
                homogeneities_no_poly.append(dict_info['homogeneity'])
            else:
                homogeneities_poly.append(dict_info['homogeneity'])

            if 'dsDNA' in dict_info["shared_taxonomy"] or 'dsDNA' in dict_info["valid_branches"]:
                homogeneities_dsDNA.append(dict_info['homogeneity'])
            else:
                homogeneities_no_dsDNA.append(dict_info['homogeneity'])

    assert (len(homogeneities_dsDNA) + len(homogeneities_no_dsDNA)== len(homogeneities) ), 'homogeneity array are no symetric'

    print(build_summary_line(homogeneities, 'all', inflation, evalue, coverage))
    print(build_summary_line(homogeneities_no_poly, 'no poly', inflation, evalue, coverage))
    print(build_summary_line(homogeneities_poly, 'poly', inflation, evalue, coverage))
    print(build_summary_line(homogeneities_dsDNA, 'dsDNA', inflation, evalue, coverage))
    print(build_summary_line(homogeneities_no_dsDNA,'no dsDNA', inflation, evalue, coverage))

    summary_fl.write(build_summary_line(homogeneities, 'all', inflation, evalue, coverage)+'\n')
    summary_fl.write(build_summary_line(homogeneities_no_poly, 'no poly', inflation, evalue, coverage)+'\n')
    summary_fl.write(build_summary_line(homogeneities_poly, 'poly', inflation, evalue, coverage)+'\n')
    summary_fl.write(build_summary_line(homogeneities_dsDNA, 'dsDNA', inflation, evalue, coverage)+'\n')
    summary_fl.write(build_summary_line(homogeneities_no_dsDNA,'no dsDNA', inflation, evalue, coverage)+'\n')

    return homogeneities

def getListFromFile(file):
    list_line = []
    with open(file, "r") as hd:
        for l in hd:
            list_line.append(l.rstrip())
    return list_line

def build_summary_line(homogeneities, type, inflation, evalue, coverage):
    try:
        median=np.median(homogeneities)
        Q1 = np.percentile(homogeneities, 25)
        Q3 = np.percentile(homogeneities, 75)
        ymax = max(homogeneities)
        ymin = min(homogeneities)
        mean=np.mean(homogeneities)
    except IndexError:
        median, Q1, Q3, ymax, ymin, mean = None, None, None, None, None, None
    length = len(homogeneities)
    line = [median,Q1, Q3, ymax,  ymin, length, mean, coverage,  evalue, inflation, type]
    line = [str(i) for i in line]
    line = ','.join(line)
    return line

if __name__ == '__main__':

    logging.basicConfig(filename='log/homogeneity_cluster.log', level=logging.WARNING)
    clusters = sys.argv[1]
    cluster_homogeneity_dir = sys.argv[2]
    taxonomy_file = sys.argv[3]  #"data/taxonomy/taxonomy_virus.txt"
    alternative_taxon_id_file = sys.argv[4] # "data/taxonomy/heterogeneous_taxon_id_taxonomy_virus.txt"
    unclassified_term_file = sys.argv[5] # "data/taxonomy/unclassified_terms.txt"
    stat_protein_file= sys.argv[6] #'results/stat_viral_protein/stat_proteins_Viruses.csv'
    summary_file =  sys.argv[7]

    #initiate summary file
    summary_fl = open(summary_file, 'w')
    header = ["median","Q1","Q3","max","min","length","mean","coverage","evalue","inflation","type"]
    summary_fl.write(','.join(header)+'\n')

    annotated_genomes = getAnnotatedProteinTaxId(stat_protein_file)
    unclassified_term_list = getListFromFile(unclassified_term_file)
    node_dict, count_leaves, leaves_taxonomy, genome_id_to_name = constructTaxonTree(taxonomy_file, unclassified_term_list)
    alternative_taxon_id =getAlternativeTaxonId(alternative_taxon_id_file)

    if os.path.isdir(clusters):
        print('clusters is a directory, so we compute homogeneity for each of the cluster file we found')
        cluster_files = (os.path.join(clusters, f) for f in os.listdir(clusters) if f.endswith('.out')) # in the directory only cluster need to be present
    else:
        cluster_files = [clusters]

    for cluster_file in cluster_files:
        # we retrieve the coverage value and the inflation from the file name
        re_result = re.search("evalue_(1e[-\d]+)coverage(\d+)_I([\d_]+).out", cluster_file)
        #if the regex doesn't work an expception will be raised
        if re_result:
            evalue=  re_result.group(1)
            coverage = re_result.group(2)
            inflation = re_result.group(3)
        else:
            raise NameError('parsing cluster file name failed %s' % cluster_file)

        # re_result = re.search("evalue_(1e[-\d]+)coverage", cluster_file)
        # evalue = None if not re_result else re_result.group(1)
        # if evalue == None:
        #     raise NameError('parsing cluster file name failed %s' % cluster_file)
        iter_clusters = clusterFileParser(cluster_file, annotated_genomes)

        if cluster_homogeneity_dir == 'None':
            csv_writer = False
        else:
            base_name = os.path.basename(cluster_file)
            cluster_homogeneity_file = os.path.join(cluster_homogeneity_dir,base_name.replace('.out', '_homogeneity.csv'))
            csv_writer = initiateOuputFile(cluster_homogeneity_file)

        writeHomogeneity(iter_clusters, inflation, evalue, coverage, unclassified_term_list, leaves_taxonomy, alternative_taxon_id, count_leaves, genome_id_to_name, node_dict)

    summary_fl.close()


import QuantLib as ql
import datetime as dt

def date_to_quantlib(date):
    if isinstance(date, dt.date):
        return ql.Date(date.day, date.month, date.year)
    elif isinstance(date, ql.Date):
        return date
    else:
        raise ValueError

def daterange(start_date, end_date):
    for n in range(int((end_date - start_date).days)):
        yield start_date + dt.timedelta(n)


from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required

from .models import Post, Group, User
from .forms import PostForm
from yatube.constants import POSTS_PER_STR
from .utils import create_page_object

def index(request):
    post_list = Post.objects.all()
    page_obj = create_page_object(request, post_list, POSTS_PER_STR)

    context = {
        'page_obj': page_obj,
    }
    return render(request, 'posts/index.html', context)

def group_posts(request, slug):
    group = get_object_or_404(Group, slug=slug)
    posts = group.group_posts.select_related('group')
    page_obj = create_page_object(request, posts, POSTS_PER_STR)

    context = {
        'group': group,
        'page_obj': page_obj,
    }
    return render(request, 'posts/group_list.html', context)

def profile(request, username):
    user = get_object_or_404(User, username=username)
    posts = user.posts.select_related('author')
    posts_count = Post.objects.filter(author__exact=user).count
    page_obj = create_page_object(request, posts, POSTS_PER_STR)

    context = {
        "username": username,
        "author": user,
        "posts_count": posts_count,
        "page_obj": page_obj,
    }
    return render(request, 'posts/profile.html', context)

def post_detail(request, post_id):
    post = get_object_or_404(Post, pk=post_id)
    posts_count = Post.objects.filter(author__exact=post.author).count
    context = {
        "post": post,
        "posts_count": posts_count,
    }
    return render(request, 'posts/post_detail.html', context)

@login_required
def post_create(request):
    form = PostForm(request.POST or None)
    context = {
        'form': form,
    }

    if form.is_valid():
        form.instance.author = request.user
        form.save()
        return redirect('posts:profile', request.user)
    return render(request, "posts/create_post.html", context)

@login_required
def post_edit(request, post_id):
    # post = Post.objects.get(pk=post_id)
    post = get_object_or_404(Post, pk=post_id)
    form = PostForm(request.POST or None, instance=post)
    if post.author != request.user:
        return redirect('posts:post_detail', post.id)
    if form.is_valid():
        post = form.save()
        form.instance.author = request.user
        post.save()
        return redirect('posts:post_detail', post_id)
    context = {
        'form': form,
        'is_edit': True,
        'post': post,
    }
    return render(request, "posts/create_post.html", context)


#!/usr/bin/env python3
import sys
from faaFileParser import *
from argChecker import *
__author__ = 'Stef Streamer'

'''
This program accepts an faa file and parses the file to determine the
following statistics:
Gene count (number of protein sequences)
Minimum Protein length (shortest amino acid sequence encountered)
Maximum Protein length (largest amino acid sequence encountered)
Average Protein length (a list of protein lengths are stored for future stats)
Count of a given term ("hypothetical" is the term for this assignment)
'''

def printHelp():
    message = "\nThis program accepts an faa file and parses the file to determine the\n" \
              "following statistics:\n" \
              "Gene count (number of protein sequences)\n" \
              "Minimum Protein length (shortest amino acid sequence encountered)\n" \
              "Maximum Protein length (largest amino acid sequence encountered)\n" \
              "Average Protein length (a list of protein lengths are stored for future stats)\n" \
              "Count of a given term in the amino acid sequence headers ('hypothetical' is the term for this assignment)" \
              "\n\nProgram usage:\n" \
              "python3 summarize_fasta.py <faaFile> <searchTerm>\nOr \n" \
              "python3 summarize_fasta.py --file=<faaFile> --searchTerm=<searchTerm>\n"

    print(message)
    exit()

'''
Main script
'''
#Check if the user asked for help
if len(sys.argv) > 1:
    if sys.argv[1] == "--help" or sys.argv[1] == "-h":
        printHelp()
        exit()

#Check that a faa file and search term has been passed to the program
argsNames = ["--file=","--searchTerm="]
if len(sys.argv) != 3:
    arguments = ArgChecker(argsNames, "summarize_fasta.py")
    arguments.printError()
    exit()

#Assign command line arguments
filePath = sys.argv[1]
searchTerm = sys.argv[2]

#check arguments and remove paramater names if present
aList = [filePath,searchTerm]
args = ArgChecker(argsNames, "summarize_fasta.py")
arguments = args.checkArgs(aList)
filePath = arguments[0]
searchTerm = arguments[1]

#Initialize the FaaFileParser object
record = FaaFileParser(filePath)
#Parse the file with the search term count argument
record.processFile(searchTerm)
#Print out the statistics
print("\nProccessing file\n")
print("Gene count = " + str(record.geneCount) + '\n')
print("Min protein length = " + str(record.minProtLen) + '\n')
print("Max protein length = " + str(record.maxProtLen) + '\n')
print("Average protein length = " + str(record.calcAvgLen()) + '\n')
print("Count of " + "\'" + searchTerm + "\'" + " = " + str(record.termGeneCount) + '\n')


"""
Parse pcap for unique IPs
"""

import ipaddress
import logging

import pyshark

class Pcap2IP():
    """
    Pcap2IP ingests a .pcap file and creates
    a list of unique IPs.
    """

    def __init__(self, fp):
        self.fp = fp
        self.cap = self.ingest_pcap()
        self.ips = self.ip_list()

    def ingest_pcap(self):
        """Ingest pcap and convert to pyshark
        readable format
        """

        return pyshark.FileCapture(self.fp)

    def ip_list(self):
        """Ingest pyshark-readable file and output
        list of uniquqe IPs. Only include IP's that
        are public.

        The pyshark IP extraction function appears to
        extract only the IPv4 address from a packet.

        RETURNS:
        --ip_list: a list of IP addresses
        """

        ip_list = []
        for packet in self.cap:

            src_ip = packet.ip.src
            dest_ip = packet.ip.dst
            logging.debug("source IP: {}".format(src_ip))
            logging.debug("destination IP: {}".format(dest_ip))

            # Check if IP's are public
            src_pub = not ipaddress.ip_address(src_ip).is_private
            dest_pub = not ipaddress.ip_address(dest_ip).is_private

            # Add IP's only if not already in list
            # and IP is public
            if src_ip not in ip_list and src_pub:
                ip_list.append(src_ip)

            if dest_ip not in ip_list and dest_pub:
                ip_list.append(dest_ip)

        logging.debug("IP list: {}".format(ip_list))
        return ip_list


'''
UI BITS TOOLS

Creates a QtGui.QWidget object containing all the UI and functionality for
creating bits.

By: Austin Baker
Date: 9/5/2013
'''
from PySide import QtCore
from PySide import QtGui

import maya.cmds as cmds

import marigold.ui.widgets.QTWidgets as QTWidgets
import marigold.ui.qtui_resources
import marigold.utility.TransformUtility as TransformUtility
import marigold.utility.NodeUtility as NodeUtility

class UIBitsTools( QtGui.QWidget ):
    def __init__( self ):
        super( UIBitsTools, self ).__init__()

        layout = QtGui.QVBoxLayout( self )

        # Build bits controls
        # Presets
        moduleRootBtn = QTWidgets.imageButton( 'Create Module', ':/riggingUI/icons/preset_module.png', [32,32] )
        moduleRootBtn.clicked.connect( lambda a=1:self.preset_buttons(a) )

        characterRootBtn = QTWidgets.imageButton( 'Create Character', ':/riggingUI/icons/preset_character.png', [32,32] )
        characterRootBtn.clicked.connect( lambda a=2:self.preset_buttons(a) )

        # Primitive buttons.
        sphereBtn = QTWidgets.imageButton( 'Sphere', ':/riggingUI/icons/bit_sphere.png', [32,32] )
        sphereBtn.clicked.connect( self.primitive_buttons )

        boxBtn = QTWidgets.imageButton( 'Box', ':/riggingUI/icons/bit_box.png', [32,32] )
        boxBtn.clicked.connect( self.primitive_buttons )

        cylinderBtn = QTWidgets.imageButton( 'Cylinder', ':/riggingUI/icons/bit_cylinder.png', [32,32] )
        cylinderBtn.clicked.connect( self.primitive_buttons )

        coneBtn = QTWidgets.imageButton( 'Cone', ':/riggingUI/icons/bit_cone.png', [32,32] )
        coneBtn.clicked.connect( self.primitive_buttons )

        torusBtn = QTWidgets.imageButton( 'Torus', ':/riggingUI/icons/bit_torus.png', [32,32] )
        torusBtn.clicked.connect( self.primitive_buttons )

        # Bit tools.
        matchTranslationBtn = QTWidgets.imageTextButton( 'Match Translation', ':/riggingUI/icons/icon_match_translation.png', [16,16] )
        matchTranslationBtn.clicked.connect( lambda a='tran':TransformUtility.matchTransforms( a ) )

        matchRotationBtn = QTWidgets.imageTextButton( 'Match Rotation', ':/riggingUI/icons/icon_match_rotation.png', [16,16] )
        matchRotationBtn.clicked.connect( lambda a='rot':TransformUtility.matchTransforms( a ) )

        matchAllBtn = QTWidgets.imageTextButton( 'Match All', ':/riggingUI/icons/icon_match_all.png', [16,16] )
        matchAllBtn.clicked.connect( lambda a='all':TransformUtility.matchTransforms( a ) )

        copyBitSettingsBtn = QTWidgets.imageTextButton( 'Copy Bit Settings', ':/riggingUI/icons/bit_copy_settings.png', [16,16] )
        copyBitSettingsBtn.clicked.connect( lambda:NodeUtility.copyBitSettings() )

        addChildBtn = QTWidgets.imageTextButton( 'Add Child', ':/riggingUI/icons/bit_add_child.png', [16,16] )
        addChildBtn.clicked.connect( lambda:NodeUtility.setBitChild() )

        deleteChildBtn = QTWidgets.imageTextButton( 'Delete Child', ':/riggingUI/icons/bit_delete_child.png', [16,16] )
        deleteChildBtn.clicked.connect( lambda:NodeUtility.deleteBitChild() )

        # Build bits layout.
        # Presets.
        presetsHeader = QTWidgets.basicLabel( 'Bit Presets', 'bold', 14, 'white', '2B2B30' )
        presetsHeader.setMinimumHeight( 30 )
        presetsHeader.setAlignment( QtCore.Qt.AlignCenter )

        presetsFrame = QTWidgets.basicFrame()
        presetsLayout = QtGui.QHBoxLayout()
        presetsLayout.addWidget( moduleRootBtn )
        presetsLayout.addWidget( characterRootBtn )
        presetsFrame.setLayout( presetsLayout )

        # Primitive buttons
        bitPrimitivesHeader = QTWidgets.basicLabel( 'Bit Primitives', 'bold', 14, 'white', '2B2B30' )
        bitPrimitivesHeader.setMinimumHeight( 30 )
        bitPrimitivesHeader.setAlignment( QtCore.Qt.AlignCenter )

        primitiveFrame = QTWidgets.basicFrame()

        primitiveLayout = QtGui.QHBoxLayout()
        primitiveLayout.addWidget( sphereBtn )
        primitiveLayout.addWidget( boxBtn )
        primitiveLayout.addWidget( cylinderBtn )
        primitiveLayout.addWidget( coneBtn )
        primitiveLayout.addWidget( torusBtn )
        primitiveFrame.setLayout( primitiveLayout )

        # Bit tools.
        bitToolsHeader = QTWidgets.basicLabel( 'Bit Tools', 'bold', 14, 'white', '2B2B30' )
        bitToolsHeader.setMinimumHeight( 30 )
        bitToolsHeader.setAlignment( QtCore.Qt.AlignCenter )

        bitToolsGrid = QtGui.QGridLayout()
        bitToolsGrid.setColumnMinimumWidth( 0, 100 )
        bitToolsGrid.setColumnMinimumWidth( 1, 100 )
        bitToolsGrid.setColumnMinimumWidth( 2, 100 )
        bitToolsGrid.setSpacing( 2 )
        bitToolsGrid.setContentsMargins( 0,0,0,0 )
        # widget, row, col
        bitToolsGrid.addWidget( matchTranslationBtn, 0, 0 )
        bitToolsGrid.addWidget( matchRotationBtn, 0, 1 )
        bitToolsGrid.addWidget( matchAllBtn, 0, 2 )
        bitToolsGrid.addWidget( copyBitSettingsBtn, 1, 0 )
        bitToolsGrid.addWidget( addChildBtn, 1, 1 )
        bitToolsGrid.addWidget( deleteChildBtn, 1, 2 )

        # Added the bits widgets to the sub-layout of the main window.
        layout.addWidget( presetsHeader )
        layout.addWidget( presetsFrame )
        layout.addWidget( bitPrimitivesHeader )
        layout.addWidget( primitiveFrame )
        layout.addWidget( bitToolsHeader )
        layout.addLayout( bitToolsGrid )

    def preset_buttons( self, inPresetType ):
        '''
        Creates a module or character root, complete with appropriate meta nodes.

        @param inPresetType: Int. 1 is a module root. 2 is a character root.
        '''
        if inPresetType == 1:
            # Create module root.

            # Create the root bit.
            rootBit = cmds.makeGLBit( objecttype='glTorus', name='moduleRoot', color=(1.0,0.1,0.1),
                                      torSides=3, torRings=8, torOtRad=1.1, torInRad=1, drawPlace=0 )
            # Create the meta node.
            # Connect the node to the bit.
            pass
        elif inPresetType == 2:
            # Create character root.

            # Create the root bit.
            rootBit = cmds.makeGLBit( objecttype='glTorus', name='characterRoot', color=(1.0,0.1,0.1),
                                      torSides=3, torRings=8, torOtRad=3.1, torInRad=3, drawPlace=1 )

            # Create the meta node.
            # Connect the node to the bit.
            pass

    def primitive_buttons( self ):
        sender = self.sender()
        bit_name = 'gl{0}'.format( sender.text() )
        cmds.makeGLBit( objecttype=bit_name )


from dataclasses import dataclass
import requests
import time

@dataclass(init=True)
class DatabricksJob:
  # Set up the API endpoint and token
  databricks_url: str
  api_token: str
  api_endpoint: str = f"{databricks_url}/api/2.0/jobs"

  def run_job_from_id(self, job_id):
    payload = {
      "job_id": job_id
    }
    # Call the job in parallel
    post_response = requests.post(f"{api_endpoint}/run-now", headers={"Authorization": f"Bearer {api_token}"}, json=payload)
    #print(post_response.content)
    return post_response.json()["run_id"]

  def discover_job_id(self, job_name):
    get_response = requests.get(f"{api_endpoint}/list", headers={"Authorization": f"Bearer {api_token}"})
    for item in get_response.json()["jobs"]:
      if item["settings"]["name"].upper() == job_name.upper():
        return item["job_id"]
    return None

  def run_job_from_name(self, job_name):
    job_id = self.discover_job_id(job_name)
    print(f"job_id={job_id}")
    run_id = self.run_job_from_id(job_id)
    print(f"run_id={run_id}")
    return run_id

  def wait_finish(self, run_id):
    # Wait finish
    while True:
        get_response = requests.get(f"{api_endpoint}/runs/get?run_id={run_id}", headers={"Authorization": f"Bearer {api_token}"})
        #print(get_response.content)
        status = get_response.json()["state"]["life_cycle_state"]
        print(f"status={status}")
        if status in ("TERMINATED", "INTERNAL_ERROR"):
            return ""
        time.sleep(5)

# And that's all you need to run:
databricks_url = "https://xxx.azuredatabricks.net"
api_token="xxx"
job_name = "job_name"

dj = DatabricksJob(databricks_url, api_token)
run_id = dj.run_job_from_name(job_name)
dj.wait_finish(run_id)


import os
import pathlib
import sys

input_file = sys.argv[1]

out_alleles = pathlib.Path('overall_alleles.tsv')
out_stats = pathlib.Path('overall_statistics.tsv')
with open(input_file, 'r') as f:
    for i in f.readlines():
        l = i.strip()
        p = pathlib.Path(l)
        for f in p.iterdir():
            if f.name == 'results_statistics.tsv':
                stat = open(f, 'r')
                if not out_stats.exists():
                    with open(out_stats, 'w') as out:
                        for l in stat.readlines():
                            out.write(l)
                        out.write('\n')
                else:
                    with open(out_stats, 'a') as out:
                        out.write(stat.readlines()[1])
                        out.write('\n')
            if f.name == 'results_alleles.tsv':
                allele = open(f, 'r')
                if not os.path.exists(output[1]):
                    with open(output[1], 'w') as out:
                        for l in allele.readlines():
                            out.write(l)
                        out.write('\n')
                else:
                    with open(output[1], 'a') as out:
                        out.write(allele.readlines()[1])
                        out.write('\n')


import random

def game (comp, you):
    if comp == you:
        return None
    elif comp == 's':
        if you == 'w':
            return False
        elif you == 'g':
            return True
    elif comp == 'w':
        if you == 's':
            return True
        elif you == 'g':
            return False
    elif comp == 'g':
        if you == 's':
            return False
        elif you == 'w':
            return True

print("Computer's turn : Snake(s) Water(w) or Gun(g) ?")
rand_no = random.randint(1,3)

if rand_no == 1:
    comp = 's'
elif rand_no == 2:
    comp = 'w'
else:
    comp = 'g'

you = input("Your turn : Snake(s) Water(w) or Gun(g) ? : ")
a = game(comp , you)
if a == None:
    print(f"The game is tie !! , Your choice : {you} , Computer choice : {comp}")
elif a :
     print(f"You win the game !! , Your choice : {you} , Computer choice : {comp}")
else :
     print(f"You loose the game !! , Your choice : {you} , Computer choice : {comp}")


import torch
from torch.nn import CrossEntropyLoss
from dataclasses import dataclass
from online_attacks.online_algorithms import (
    create_online_algorithm,
    compute_competitive_ratio,
    OnlineParams,
    AlgorithmType,
    compute_indices,
)
from online_attacks.classifiers.mnist import (
    load_mnist_classifier,
    MnistModel,
    load_mnist_dataset,
)
from online_attacks.attacks import (
    create_attacker,
    Attacker,
    AttackerParams,
    compute_attack_success_rate,
)
from online_attacks.attacks.pgd import PGDParams
from online_attacks.datastream import datastream
from online_attacks.utils.parser import ArgumentParser
import numpy as np
import os
import torchvision
import math

@dataclass
class MnistParams:
    attacker_type: Attacker = Attacker.FGSM_ATTACK
    attacker_params: AttackerParams = AttackerParams()
    online_params: OnlineParams = OnlineParams(
        K=100, online_type=AlgorithmType.STOCHASTIC_VIRTUAL
    )
    model_dir: str = "/checkpoint/hberard/OnlineAttack/pretained_models/"

def main(args, params: MnistParams = MnistParams()):
    dataset = load_mnist_dataset(train=False)
    dataset = datastream.PermuteDataset(
        dataset, permutation=np.random.permutation(len(dataset))
    )

    target_classifier = load_mnist_classifier(
        args.model_type,
        index=0,
        model_dir=params.model_dir,
        device=args.device,
        eval=True,
    )
    source_classifier = load_mnist_classifier(
        args.model_type,
        index=1,
        model_dir=params.model_dir,
        device=args.device,
        eval=True,
    )

    criterion = CrossEntropyLoss(reduction="none")

    attacker = create_attacker(
        source_classifier, params.attacker_type, params.attacker_params
    )

    transform = datastream.Compose(
        [
            datastream.ToDevice(args.device),
            datastream.AttackerTransform(attacker),
            datastream.ClassifierTransform(target_classifier),
            datastream.LossTransform(criterion),
        ]
    )
    target_stream = datastream.BatchDataStream(
        dataset, batch_size=1000, transform=transform
    )

    transform = datastream.Compose(
        [
            datastream.ToDevice(args.device),
            datastream.AttackerTransform(attacker),
            datastream.ClassifierTransform(source_classifier),
            datastream.LossTransform(criterion),
        ]
    )
    source_stream = datastream.BatchDataStream(
        dataset, batch_size=1000, transform=transform
    )

    params.online_params.N = len(dataset)
    offline_algorithm, online_algorithm = create_online_algorithm(params.online_params)
    print("Computing indices...")
    source_online_indices, source_offline_indices = compute_indices(
        source_stream, [online_algorithm, offline_algorithm], pbar_flag=True
    )
    target_offline_indices = compute_indices(
        target_stream, [offline_algorithm], pbar_flag=True
    )[0]

    print("Computing Competitive Ratio...")
    comp_ratio = compute_competitive_ratio(
        source_online_indices, source_offline_indices
    )
    print(
        "Comp ratio source online vs target online: %.2f"
        % (comp_ratio / params.online_params.K)
    )
    comp_ratio = compute_competitive_ratio(
        source_online_indices, target_offline_indices
    )
    print(
        "Comp ratio source online vs target offline: %.2f"
        % (comp_ratio / params.online_params.K)
    )
    comp_ratio = compute_competitive_ratio(
        source_offline_indices, target_offline_indices
    )
    print(
        "Comp ratio target online vs target offline: %.2f"
        % (comp_ratio / params.online_params.K)
    )

    transform = datastream.Compose(
        [
            datastream.ToDevice(args.device),
            datastream.AttackerTransform(attacker),
            datastream.ClassifierTransform(target_classifier),
        ]
    )
    target_stream = datastream.BatchDataStream(
        dataset, batch_size=1000, transform=transform
    )

    random_indices = np.random.permutation(len(dataset))[: params.online_params.K]
    stream = target_stream.subset(random_indices)
    fool_rate = compute_attack_success_rate(stream)
    print("Attack success rate (Random): %.4f" % fool_rate)

    source_online_indices = [x[1] for x in source_online_indices]
    stream = target_stream.subset(source_online_indices)
    fool_rate = compute_attack_success_rate(stream)
    print("Attack success rate (Source Online): %.4f" % fool_rate)

    source_offline_indices = [x[1] for x in source_offline_indices]
    stream = target_stream.subset(source_offline_indices)
    fool_rate = compute_attack_success_rate(stream)
    print("Attack success rate (Source Offline): %.4f" % fool_rate)

    target_offline_indices = [x[1] for x in target_offline_indices]
    stream = target_stream.subset(target_offline_indices)
    fool_rate = compute_attack_success_rate(stream)
    print("Attack success rate (Source Offline): %.4f" % fool_rate)

    stream = datastream.BatchDataStream(dataset)
    stream = stream.subset(source_online_indices, batch_size=len(source_online_indices))
    x, target = next(stream)
    output_dir = os.path.join(args.output_dir, "img")
    os.makedirs(output_dir, exist_ok=True)
    torchvision.utils.save_image(
        x,
        os.path.join(output_dir, "clean.png"),
        nrow=int(math.sqrt(len(source_online_indices))),
    )

    x = attacker.perturb(x.to(args.device))
    torchvision.utils.save_image(
        x,
        os.path.join(output_dir, "attack.png"),
        nrow=int(math.sqrt(len(source_online_indices))),
    )

    return comp_ratio

if __name__ == "__main__":
    parser = ArgumentParser()

    # Online algorithm arguments
    parser.add_config("mnist_params", MnistParams)

    # MNIST dataset arguments
    parser.add_argument("--data_dir", default="./data")
    parser.add_argument("--test", action="store_true")
    parser.add_argument("--output_dir", default="./results")

    # MNIST classifier arguments
    parser.add_argument(
        "--model_type", type=MnistModel, default=MnistModel.MODEL_A, choices=MnistModel
    )

    # Attacker arguments
    parser.add_argument(
        "--attacker", type=Attacker, default=Attacker.PGD_ATTACK, choices=Attacker
    )

    args = parser.parse_args()
    args.device = (
        torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
    )

    main(args, args.mnist_params)


degrees = int(input())
time = input()
#if time == 'Morning' or time == 'Afternoon' or
outfit = ''
shoes = ''
if time == 'Morning':
    if 10 <= degrees <= 18:
        outfit = 'Sweatshirt'
        shoes = Shoes = 'Sneakers'
    elif 18 < degrees <= 24:
        outfit = 'Shirt'
        shoes = 'Moccasins'
    else:
        outfit = 'T-Shirt'
        shoes = 'Sandals'
elif time == 'Afternoon':
    if 10 <= degrees <= 18:
        outfit = 'Shirt'
        shoes = Shoes = 'Moccasins'
    elif 18 < degrees <= 24:
        outfit = 'T-Shirt'
        shoes = 'Sandals'
    else:
        outfit = 'Swim Suit'
        shoes = 'Barefoot'
elif time == 'Evening':
    if 10 <= degrees <= 18:
        outfit = 'Shirt'
        shoes = Shoes = 'Moccasins'
    elif 18 < degrees <= 24:
        outfit = 'Shirt'
        shoes = 'Moccasins'
    else:
        outfit = 'Shirt'
        shoes = 'Moccasins'
else:
    print('Error')
print(f"It's {degrees} degrees, get your {outfit} and {shoes}.")

soft_uni_basic/Conditional Statements/advanced/exercise/02. Summer Outfit.py


import skimage.segmentation as seg
from skimage import io
from skimage.transform import resize
from skimage.color import rgb2hsv, rgb2gray
import numpy as np
from PIL import Image
from scipy.spatial.distance import cosine as cosine_distance

class BipedModel:
    def __init__(self):
        pass

    def extract_features(self, img_path, return_isolated=False):
        image = self.isolate_sock(img_path)
        features = self.histogram(image)

        if return_isolated:
            return features, image

        return features

    def resize(self, image):
        # first resize the image for quicker calculations
        aspect_ratio = image.shape[1] / image.shape[0]
        resized_height = 100
        resized_width = int(resized_height * aspect_ratio)
        image = resize(image, (resized_height, resized_width), anti_aliasing=False, preserve_range=True)
        return image

    def isolate_sock(self, img_path, method='morphological_chan_vese'):
        image = io.imread(img_path)
        image = self.resize(image)
        # low min size factor because we don't want the algo to drop a segment
        methods = {
            'slic': self.slic,
            'morphological_chan_vese': self.morphological_cv,
        }
        mask = methods[method](image)
        mask = mask != 0
        image = image * np.repeat(mask[:, :, np.newaxis], 3, axis=2)
        image = image.astype(int)
        return image

    def get_similarity(self, feature1, feature2, method='intersection'):
        if method == 'cosine':
            similarity = 1-cosine_distance(feature1, feature2)

        elif method == 'intersection':
            minima = np.minimum(feature1, feature2)
            maxima = np.maximum(feature1, feature2)
            similarity = np.true_divide(np.sum(minima), np.sum(maxima))
        return similarity

    def histogram(self, image, method='3D_rgb'):
        image = image.astype(int)
        # make a linear image with all the unmasked pixels
        sock = image[np.nonzero(image.mean(axis=2))]
        sock = sock[:, np.newaxis, :]
        sock = np.uint8(sock)

        if method == 'pil_histogram':
            pil_image = Image.fromarray(sock)
            histogram = pil_image.histogram()

        if method == '3D_rgb':
            sock = sock[:, 0, :]
            bins_per_dim = 17
            bin_limits = [pow(255, i/bins_per_dim)for i in range(bins_per_dim+1)]
            bin_limits[0] = 0
            bins = np.array([bin_limits, bin_limits, bin_limits])
            histogram, edges = np.histogramdd(sock, bins=bins)

        if method == 'hue':
            sock = rgb2hsv(sock)
            sock = sock[:, 0, :]
            weights = sock[:, 1] * sock[:, 2]  # value times saturation
            histogram, edges = np.histogramdd(sock[:, 0], bins=[40], weights=weights)

        return np.array(histogram).flatten()

    def slic(self, image):
        mask = seg.slic(image, n_segments=2, min_size_factor=0.01, max_size_factor=2, compactness=10)
        return mask

    def morphological_cv(self, image):
        image = rgb2gray(image)
        mask = seg.morphological_chan_vese(image, iterations=1000, init_level_set='circle')
        return mask


import numpy as np

class EnKF:
    """
    Class for implementing the Ensemble Kalman Filter.

    Attributes
    ------------
    nvars: int
        Number of variables in state vector
    loc: int
        Localization distance
    gamma: float
        Covariance inflation factor
    nens:
        Ensemble size
    """

    def __init__(self, N=None, loc=None, gamma=1, nens=1):
        self.nvars = N
        self.loc = loc
        self.gamma = gamma
        self.nens = nens

    def obs(self, h, x):
        """
        Execute observation operator

        :param h: list of variable indices to be observed
        :param x: state vector
        :return: h(x)
        """

        ret = x[list(h)]
        return (ret)

    def getK(self, cov, h, r):
        """
        Compute Kalman gain matrix

        :param cov: forecast covariance
        :param h: list of variables to be observed
        :param r: observation standard deviation
        :return: kalman gain matrix
        """
        k = self.obs(h, cov.transpose())
        k = k.transpose()
        second_term = self.obs(h, k)
        variance = r**2
        for i in range(len(h)):
            second_term[i, i] = second_term[i, i] + variance
        second_term = np.linalg.inv(second_term)
        k = np.dot(k, second_term)
        return (k)

    def localize(self, cov):
        """
        Localize the covariance matrix according to self.loc. If distance>loc, set cov=0.
        :param cov: covariance matrix to localize
        :return: localized covariance matrix
        """
        loc = self.loc
        for i in range(self.nvars):
            for j in range(self.nvars):
                mid = abs(i - j)
                if i > j:
                    out = self.nvars - i
                    out = out + j
                if i < j:
                    out = self.nvars - j
                    out = out + i
                if i == j:
                    out = 0
                dist = min([mid, out])
                if dist > loc:
                    cov[i, j] = 0
        return (cov)

    def assimilate(self, xf, y, h, cov, r):
        """
        assimilate an observation.

        :param xf: forecast
        :param y: observation
        :param h: observation operator list
        :param cov: forecast covariance
        :param r: observation error standard deviation
        :return: posterior estimate of x
        """
        cov = self.localize(cov)
        cov = cov * self.gamma
        k = self.getK(cov, h, r)
        xp = xf + np.dot(k, self.obs(h, y) - self.obs(h, xf))
        return (xp)

    def ensemble_assim(self, xf, y, h, r):
        """
        Perform ensemble assimilation.

        :param xf: Forecast ensemble
        :param y: Observation vector
        :param h: observation operator list
        :param r: observation error standard deviation
        :return: posterior ensemble
        """
        cov = np.cov(xf)
        D = np.zeros([self.nvars, self.nens])
        for i in range(self.nvars):
            D[i, :] = y[i].values + np.random.normal(0, r, self.nens)
        xp = self.assimilate(xf, D, h, cov, r)
        return (xp)


'''
Дан файл с таблицей в формате TSV с информацией о росте школьников разных классов.
Напишите программу, которая прочитает этот файл и подсчитает для каждого класса средний рост учащегося.
Файл состоит из набора строк, каждая из которых представляет собой три поля:
Класс Фамилия Рост
Класс обозначается только числом. Буквенные модификаторы не используются. Номер класса может быть от 1 до 11 включительно. В фамилии нет пробелов, а в качестве роста используется натуральное число, но при подсчёте среднего требуется вычислить значение в виде вещественного числа.
Выводить информацию о среднем росте следует в порядке возрастания номера класса (для классов с первого по одиннадцатый). Если про какой-то класс нет информации, необходимо вывести напротив него прочерк, например:
Sample Input:
6	Вяххи	159
11	Федотов	172
7	Бондарев	158
6	Чайкина	153
Sample Output:
1 -
2 -
3 -
4 -
5 -
6 156.0
7 158.0
8 -
9 -
10 -
11 172.0
'''

class_rawinfo = {}
#new_heights = 0
#new_students = 0
class_info = ['-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-']
#class_info = []

with open('input_3.7.5.txt') as in_f_obj:
	for line in in_f_obj:
		#print(line)
		string = line.rstrip().split('\t')
		#print(string)
#n = int(input())
#for _ in range(n):
	#string = input().split('	')
		if string[0] not in class_rawinfo:
			class_rawinfo[string[0]] = [int(string[2]), 1]
		elif string[0] in class_rawinfo:
			heights = class_rawinfo[string[0]][0] + int(string[2])
			students = class_rawinfo[string[0]][1] + 1
			class_rawinfo[string[0]] = [heights, students]

for k, v in class_rawinfo.items():
	#print(k, str(v[0] / v[1]))
	#list.pop([int(k)-1])
	class_info[int(k)-1] = v[0] / v[1]
	#print(class_rawinfo)
	#print(class_info)

with open('output_3.7.5.txt', 'w') as out_f_obj:
	for i in range(len(class_info)):
		#print(i+1, str(class_info[i]))
		output = str(i+1) + ' ' + str(class_info[i]) + '\n'
		#print(output)
		out_f_obj.write(output)

#
# Делаем словарь {1:[0,0], 2:[0,0]... 11:[0,0]}, где [0:0] = [сумма ростов : кол-во учеников]
tab = {i: [0, 0] for i in range(1, 12)}

with open('input_3.7.5.txt') as inf:
	# Заполняем словарь:
	for i in inf:
		line = i.strip().split('\t')
		tab[int(line[0])][0] += int(line[2])  # tab[класс][0] += рост ученика
		tab[int(line[0])][1] += 1  # tab[класс][1] += 1 (счетчик учеников в классе)

	# Распечатка:
	for i in tab.keys():
		if tab[i][1] == 0:
			print(i, '-')  # распечатываем класс, в котором нет учеников
		else:
			# считаем и распечатываем средний рост для i-го класса:
			print(i, (tab[i][0] / tab[i][1]))

#
import pandas as pd

df = pd.read_table('C:\\Users\\User\\Desktop\\py_course\\input_3.7.5.txt', header=None, sep=r'\s{1,}')
print(df.groupby(0).mean())

#
classes = {i: [] for i in range(1, 12)}
with open('input_3.7.5.txt') as f:
    for line in f:
        cls, name, height = line.strip().split('\t')
        classes[int(cls)].append(int(height))

for cls, heights in classes.items():
    print(cls, sum(heights) / len(heights) if heights else '-')

#
c = {str(i):[0]*2 for i in range(1,12)}
for l in open('in'):
    s = l.strip().split()[::2]
    c[s[0]] = [c[s[0]][0] + int(s[1]), c[s[0]][1] + 1]
[print(k + ' ', v[0]/v[1] if v[0] else '-') for k, v in c.items()]

#
d = {i:[0, 0] for i in range(1, 12)}
with open('input_3.7.5.txt') as a:
    for i in a:
        s = i.split('\t')
        sum_height = d[int(s[0])][0] + int(s[2])
        n = d[int(s[0])][1] + 1
        d[int(s[0])] = [sum_height, n]
for i in d:
    if d[i][1] == 0:
        print(i, '-')
    else:
        print(i, d[i][0]/d[i][1])

#
d = dict()
with open('dataset.txt') as inf:
    for line in inf:
        a, b, c = line.strip().split( )
        d.setdefault(a, []).append(int(c))

k = 1

while k <= 11:
    if str(k) in d.keys():
        print(k, sum(d.get(str(k)))/len(d.get(str(k))))
    else:
        print(k, '-')
    k += 1

#
inf = [l.split('\t') for l in open('file.txt', 'r').read().strip().splitlines()]
#подгружаем файл в виде массива вида [[класс, фамилия, рост],...]

d = {}                       					#создаём словарь вида{класс:суммарный рост}
for y in range(1,12):      						#для каждого класса
    d[y], c = 0, 0        						#создаём нулевое значение суммарного роста и счётчика учеников
    for i in inf:       						#пробегаемся по строкам файла
        if int(i[0]) == y:    					#если нашли нужный класс, то
            d[y] += int(i[2]) 					#увеличиваем суммарный рост
            c += 1       						#увеличиваем счётчик количества учеников
    print(y, '-' if c == 0 else str(d[y] / c))	#печатаем класс и средний рост

#
from collections import defaultdict
from statistics import mean
with open('dataset_xxx.txt') as file:
    dct = defaultdict(list)
    for line in file:
        cl, _, growth = line.split()
        dct[int(cl)] += [int(growth)]
    for key, value in sorted(dct.items()):
        print(key, mean(value))

stepik - Программирование на Python/3. Функции. Словари. Файлы. Модули/main 3.7.5.py


import plotly.graph_objects as go
import dash
from dash import dcc, html
import csv
from PIL import Image
import numpy as np

# Read data from the CSV file
data = []
with open("spacetimecuberoutes/amsnonpeakhour.csv", "r") as csvfile:
    reader = csv.DictReader(csvfile)
    for row in reader:
        data.append(row)

# Function to split latitude and longitude values from a string in "lat, long" format
def split_lat_long(lat_long_str):
    lat, long = map(float, lat_long_str.split(','))
    return lat, long

# Create lists to store data for Plotly
origin_lat_long = []
destination_lat_long = []
distances = []
driving_times = []
public_transport_times = []
transport_modes = []
route_names = []  # New list to store route names

# Extract data from the CSV and split into separate lists for Plotly
for row in data:
    o_lat, o_long = split_lat_long(row['origin_latitude'] + ',' + row['origin_longitude'])
    origin_lat_long.append((o_lat, o_long))

    d_lat, d_long = split_lat_long(row['destination_latitude'] + ',' + row['destination_longitude'])
    destination_lat_long.append((d_lat, d_long))

    distances.append(float(row['distance']))  # Assuming the CSV contains a 'distance' column
    driving_times.append(float(row['driving_time']))  # Assuming the CSV contains a 'driving_time' column
    public_transport_times.append(float(row['public_transport']))  # Assuming the CSV contains a 'public_transport' column
    transport_modes.append(int(row['transport_modes']))  # Assuming the CSV contains a 'transport_modes' column
    route_names.append(row['route_name'])  # Assuming the CSV contains a 'route_name' column

# Initialize the Dash app
app = dash.Dash(__name__)

# Read the image you want to use as the texture
im = np.asarray(Image.open("spacetimecuberoutes/bengaluru_map.jpg"))  # reading image as a numpy array
im_x, im_y, im_layers = im.shape

# Create a meshgrid of x and y values spanning the image dimensions
x, y = np.meshgrid(np.linspace(0, im_x - 1, im_x), np.linspace(0, im_y - 1, im_y))

# Create a surface mesh from the image
surface = go.Surface(
    x=x,
    y=y,
    z=np.zeros(im.shape[:2]),
    surfacecolor=im,
    showscale=False,
    colorscale='Viridis',
    opacity=0.7,
)

# Create a scatter trace for route lines and origin-destination points
traces = []
for i in range(len(data)):
    x_vals = [origin_lat_long[i][1], destination_lat_long[i][1]]
    y_vals = [origin_lat_long[i][0], destination_lat_long[i][0]]
    z_vals = [driving_times[i]] * 2
    time_color = '#8B0000' if z_vals[0] > 90 else 'green' if z_vals[0] <= 30 else '#FFD700' if z_vals[
                                                                                0] <= 60 else 'red'
    line_width = 2 + z_vals[0] / 30

    traces.append({
        'type': 'scatter3d',
        'x': x_vals,
        'y': y_vals,
        'z': z_vals,
        'mode': 'lines',
        'line': {'color': time_color, 'width': line_width},
        'hoverinfo': 'text',
        'text': 'Route: {}
Distance: {} km
Time: {} mins'.format(route_names[i], distances[i], z_vals[0])
    })

    traces.append({
        'type': 'scatter3d',
        'x': [origin_lat_long[i][1], destination_lat_long[i][1]],
        'y': [origin_lat_long[i][0], destination_lat_long[i][0]],
        'z': z_vals,
        'mode': 'markers',
        'marker': {'size': 5, 'color': ['#00B295', '#23395B']},
        'name': 'Origin-Destination Points'
    })

# Set the layout
app.layout = html.Div([
    dcc.Graph(
        id='space-time-cube',
        figure={
            'data': traces + [surface],
            'layout': {
                'title': 'Space-Time Cube',
                'scene': {
                    'xaxis_title': 'Longitude',
                    'yaxis_title': 'Latitude',
                    'zaxis_title': 'Time (mins)',
                    'zaxis': {'tickvals': list(range(0, 200, 25)), 'ticktext': [str(i) for i in range(0, 200, 25)]},
                    'xaxis': {'range': [0, im_x], 'showticklabels': False},
                    'yaxis': {'range': [0, im_y], 'showticklabels': False},
                    'camera_eye': {'x': -1.5, 'y': -2.5, 'z': 1},
                    'bgcolor': 'rgb(240, 240, 240)',
                }
            }
        },
        style={'width': '100%', 'height': '100vh'}
    )
])

if __name__ == '__main__':
    app.run_server(debug=True)


"""
Given an n x n binary matrix grid, return the length of the shortest clear path in the matrix. If there is no clear path, return -1.

A clear path in a binary matrix is a path from the top-left cell (i.e., (0, 0)) to the bottom-right cell (i.e., (n - 1, n - 1)) such that:

All the visited cells of the path are 0.
All the adjacent cells of the path are 8-directionally connected (i.e., they are different and they share an edge or a corner).
The length of a clear path is the number of visited cells of this path.

"""

from collections import deque

class Solution:
    def shortestPathBinaryMatrix(self, grid: List[List[int]]) -> int:

        if not grid or (not grid[0]):
            return -1

        if grid[0][0] != 0:
            return -1

        queue = deque()
        start = (0,0,1)

        visied = [[False] * len(grid[0]) for _ in range(len(grid))]
        queue.append(start)
        visied[0][0] = True

        end_i, end_j = len(grid)-1, len(grid[0])-1

        while queue:
            i,j, level = queue.popleft()

            if (i,j) == (end_i, end_j):
                return level

            for i_step, j_step in [(-1,-1), (-1,0), (-1,1), (0,1), (1,1), (1,0), (1,-1), (0,-1)]:
                next_i, next_j = i + i_step, j + j_step

                if 0 <= next_i < len(grid) and 0 <= next_j < len(grid) and (not visied[next_i][next_j]) and grid[next_i][next_j] == 0:
                    queue.append((next_i, next_j, level + 1))
                    visied[next_i][next_j] = True

        return -1


#  Bonusoppgave 1 - Dart 2.0

import random

num_of_players = int(input("Enter number of players:"))
num_of_darts = int(input("Enter number of darts:"))
num_of_rounds = int(input("Enter number of rounds:"))

player_score = 0
dart_scores = []
total_score = []

#  dart_scores = [i for i in range(1, 21)] + [2*i for i in range(1, 21)] + [3*i for i in range(1,21)] + [0, 25, 50]
#  Ville forenkle måten å bygge listen dart_scores på, leste meg frem til denne metoden over - list comprehension.
#  Synes det er en enkel og pen måte å bygge listen på. Endte opp med å bruke en for-løkke til å gjøre samme sak.

for i in range(1, 21):
    dart_scores.append(i)
    dart_scores.append(2*i)
    dart_scores.append(3*i)

dart_scores.append(0)
dart_scores.append(25)
dart_scores.append(50)

for num in range(num_of_players):
    print(f"**Player {num +1}**")
    for rounds in range(num_of_rounds):
        print(f"Round {rounds +1}")
        for throws in range(num_of_darts):
            score = random.choice(dart_scores)
            print(score)
            player_score += score
    total_score.append(player_score)
    player_score = 0

print(f"The total score is {total_score}")


import subprocess
from threading import Timer
import time
import sys

def Execute(command, fin, timeLimit):
    """
    Execute(command : list[string], fin : File, reqId : str) : tuple(result: int, output: string, error: string)

    -result
        0: success
        1: runtime error
        2: timeout

    executes given command with subprocess module.
    """

    p = subprocess.Popen(command, stdin = fin, stdout = subprocess.PIPE, stderr = subprocess.PIPE, text = True)
    timer = Timer(timeLimit * 2 / 1000.0, p.kill)

    timer.start()

    startTime = time.time()
    output, error = p.communicate()
    endTime = time.time()

    #print(str(endTime - startTime), file = sys.stderr)

    if timer.is_alive():
        timer.cancel()
    else:
        return 2, output, str(endTime - startTime)

    if endTime - startTime > timeLimit / 1000.0:
        return 2, output, str(endTime - startTime)

    if len(error) > 0:
        return 1, output, error

    return 0, output, error


from django.test import TestCase

import mock
from oauthlib.common import Request

from ..oauth2_validators import OAuth2Validator
from ..models import get_application_model
from ..compat import get_user_model

UserModel = get_user_model()
AppModel = get_application_model()

class TestOAuth2Validator(TestCase):
    def setUp(self):
        self.user = UserModel.objects.create_user("user", "test@user.com", "123456")
        self.request = mock.MagicMock(wraps=Request)
        self.request.client = None
        self.validator = OAuth2Validator()
        self.application = AppModel.objects.create(
            client_id='client_id', client_secret='client_secret', user=self.user,
            client_type=AppModel.CLIENT_PUBLIC, authorization_grant_type=AppModel.GRANT_PASSWORD)

    def tearDown(self):
        self.application.delete()

    def test_authenticate_request_body(self):
        self.request.client_id = 'client_id'
        self.request.client_secret = ''
        self.assertFalse(self.validator._authenticate_request_body(self.request))

        self.request.client_secret = 'wrong_client_secret'
        self.assertFalse(self.validator._authenticate_request_body(self.request))

        self.request.client_secret = 'client_secret'
        self.assertTrue(self.validator._authenticate_request_body(self.request))

    def test_extract_basic_auth(self):
        self.request.headers = {'HTTP_AUTHORIZATION': 'Basic 123456'}
        self.assertEqual(self.validator._extract_basic_auth(self.request), '123456')
        self.request.headers = {}
        self.assertIsNone(self.validator._extract_basic_auth(self.request))
        self.request.headers = {'HTTP_AUTHORIZATION': 'Dummy 123456'}
        self.assertIsNone(self.validator._extract_basic_auth(self.request))

    def test_authenticate_client_id(self):
        self.assertTrue(self.validator.authenticate_client_id('client_id', self.request))

    def test_authenticate_client_id_fail(self):
        self.application.client_type = AppModel.CLIENT_CONFIDENTIAL
        self.application.save()
        self.assertFalse(self.validator.authenticate_client_id('client_id', self.request))
        self.assertFalse(self.validator.authenticate_client_id('fake_client_id', self.request))

    def test_client_authentication_required(self):
        self.request.headers = {'HTTP_AUTHORIZATION': 'Basic 123456'}
        self.assertTrue(self.validator.client_authentication_required(self.request))
        self.request.headers = {}
        self.request.client_id = 'client_id'
        self.request.client_secret = 'client_secret'
        self.assertTrue(self.validator.client_authentication_required(self.request))
        self.request.client_secret = ''
        self.assertFalse(self.validator.client_authentication_required(self.request))
        self.application.client_type = AppModel.CLIENT_CONFIDENTIAL
        self.application.save()
        self.request.client = ''
        self.assertTrue(self.validator.client_authentication_required(self.request))


import logging

import discord
from discord.channel import TextChannel, VoiceChannel
from discord.ext.commands.context import Context
from discord.guild import Guild
from discord_slash.context import SlashContext

from cogs.state_manager import StateManager
from src.music.player import Player

class MusicBoxContext(Context):
    r"""Inherits a discord context :class:`~discord.ext.commands.context` to
    include Music Box native elements and some SlashContext fields.

    Represents the context in which a command is being invoked under.

    This class contains a lot of meta data to help you understand more about
    the invocation context.

    ## New fields

    profile: :class:`.Profile`
        The profile matched to this context
    slash_context: :class:`slash_context`
        ShashContext if context is a slash command. Defaults to none. Similar to prefix.

    # Imported fields

    message: :class:`.Message`
        The message that triggered the command being executed.
    args: :class:`list`
        The list of transformed arguments that were passed into the command.
        If this is accessed during the :func:`on_command_error` event
        then this list could be incomplete.
    kwargs: :class:`dict`
        A dictionary of transformed arguments that were passed into the command.
        Similar to :attr:`args`\, if this is accessed in the
        :func:`on_command_error` event then this dict could be incomplete.
    prefix: :class:`str`
        The prefix that was used to invoke the command.
    command: :class:`Command`
        The command that is being invoked currently.
    invoked_with: :class:`str`
        The command name that triggered this invocation. Useful for finding out
        which alias called the command.
    invoked_subcommand: :class:`Command`
        The subcommand that was invoked.
        If no valid subcommand was invoked then this is equal to ``None``.
    subcommand_passed: Optional[:class:`str`]
        The string that was attempted to call a subcommand. This does not have
        to point to a valid registered subcommand and could just point to a
        nonsense string. If nothing was passed to attempt a call to a
        subcommand then this is set to ``None``.
    command_failed: :class:`bool`
        A boolean that indicates if the command failed to be parsed, checked,
        or invoked.
    bot: :class:`.Bot`
        The bot that contains the command being executed.
    """

    def __init__(self, **attrs):
        """Inherits a discord context :class:`~discord.ext.commands.context` to
        include Music Box native elements and some SlashContext fields.

        ## New fields

        guild: :class:`.Guild`
            The guild matched to this context
        command_channel: :class:`.TextChannel`
            The command_channel matched to this context
        player: :class:`.Player`
            The player matched to this context
        slash_context: :class:`slash_context`
            ShashContext if context is a slash command. Defaults to none. Similar to prefix.

        ## Essential attributes used in the MusicBox program

        prefix: :class:`str`
            The prefix that was used to invoke the command. Defaults to empty string.
            If context was a slash command, this should be '/'
        message: :class:`.Message`
            The message that triggered the command being executed.
        state: :class:`.StateManager`
            The state manager that is handling the current state of the bot.
        args: :class:`list`
            The list of transformed arguments that were passed into the command.
        kwargs: :class:`dict`
            A dictionary of transformed arguments that were passed into the command.

        """
        self.guild: Guild = attrs.pop("guild")
        self.command_channel: TextChannel = attrs.pop("command_channel", None)
        self.player: Player = attrs.pop("player", None)
        self.name: str = attrs.pop("name", "")
        self.slash_context: SlashContext = attrs.pop("slash_context", None)
        self.state: StateManager = attrs.pop("state", None)

        # workaround: make _state object since super() expects one (regardless of message=null)
        class FakeMessage(NotImplementedError):
            pass

        if attrs["message"] is None:
            a = FakeMessage()
            a._state = NotImplementedError
            attrs["message"] = a

        super().__init__(**attrs)

        # fix self.message to not be a fake message
        if isinstance(self.message, FakeMessage):
            self.message = None
        self.verify_context()

    def get_str_full_input(self) -> str:
        if self.message:
            return self.message.content
        elif self.prefix == "/":
            return self.prefix + self.name + " " + " ".join(self.args)

    def get_author(self) -> discord.Member:
        if self.message:
            return self.message.author
        elif self.slash_context:
            if self.slash_context.author:
                return self.slash_context.author

        return None

    def get_guild(self) -> discord.Guild:
        if self.guild:
            return self.guild
        elif self.prefix == "/":
            if self.slash_context.guild:
                return self.slash_context.guild

        return None

    def verify_context(self) -> bool:
        """Checks to make sure fields are consistant and valid. Returns true if valid, throws error if not."""
        if bool(self.slash_context) != (self.prefix == "/"):
            raise Exception("slash_context and prefix values do not line up")
        return True

    def determine_voice_channel(self) -> discord.VoiceChannel:
        """Tries to determine the voice channel to connect the player given context.

        Returns:
            discord.VoiceChannel: Determined voice channel
            None: No suitable voice channel (player is already connected, no suitable voice_channels were given)
        """
        last_connected_channel = None
        first_voice_channel = None

        # Priority is based from the code top to bottom. if state is true (CheckStateTrue sections) then code will end determination

        # Check player exists
        # no voice channel needed if player already connected
        if (
            self.player
            and self.player.connected_client
            and self.player.connected_client.is_connected()
        ):
            return None

        # Check slash_context exists
        if self.slash_context:
            voice_channel_slash_context = (
                self.return_voice_channel_for_slash_context()
            )
            if voice_channel_slash_context:
                return voice_channel_slash_context

        # Check message exists
        if self.message:
            voice_channel: discord.VoiceChannel
            voice_channel = self.return_voice_channel_for_message()
            if voice_channel:
                return voice_channel

        # CheckStateTrue: join last_connected_channel if exists
        if last_connected_channel:
            logging.warn(
                f"Determined last connected channel for guild {self.guild.name}"
            )
            return last_connected_channel

        if self.guild and self.guild.voice_channels:
            first_voice_channel = self.guild.voice_channels[0]

        # Join first available voice channel if exists
        if first_voice_channel:
            logging.warn(
                f"Determined first available voice channel for guild {self.guild.name}"
            )
            return first_voice_channel

        logging.error("Determined no possible voice channel to join.")
        return None

    def return_voice_channel_for_slash_context(self) -> VoiceChannel:
        """Looks at the slash_context object and decides if a voice channel exists

        Returns:
            VoiceChannel: The voice_channel associated with the slash_context
        """
        # CheckStateTrue: join slash command user voice_channel if author in voice channel
        if self.slash_context.author:
            if self.guild:
                for voice_channel in self.guild.voice_channels:
                    if (
                        self.slash_context.author.id
                        in voice_channel.voice_states.keys()
                    ):  # this will get raw uncached voice states
                        return voice_channel

    def return_voice_channel_for_message(self) -> VoiceChannel:
        """Looks at the message object and decides if a voice channel exists

        Args:
            voice_channel (VoiceChannel): The voice_channel associated with the message

        Returns:
            VoiceChannel: The voice_channel associated with the message
        """
        # CheckStateTrue: join message authors voice_channel if author in voice channel
        for voice_channel in self.message.guild.voice_channels:
            if self.message.author.id in voice_channel.voice_states.keys():
                return voice_channel


from distutils.core import setup
from pathlib import Path

this_directory = Path(__file__).parent
install_requires = (this_directory / "requirements.txt").read_text().splitlines()
long_description = (this_directory / "README.md").read_text()

exec(open("satflow/version.py").read())
setup(
    name="satflow",
    version=__version__,
    packages=["satflow", "satflow.data", "satflow.models"],
    url="https://github.com/openclimatefix/satflow",
    license="MIT License",
    company="Open Climate Fix Ltd",
    author="Jacob Bieker",
    install_requires=install_requires,
    long_description=long_description,
    ong_description_content_type="text/markdown",
    author_email="jacob@openclimatefix.org",
    description="Satellite Optical Flow",
    classifiers=[
        "Development Status :: 4 - Beta",
        "Intended Audience :: Developers",
        "Topic :: Scientific/Engineering :: Artificial Intelligence",
        "License :: OSI Approved :: MIT License",
        "Programming Language :: Python :: 3.8",
    ],
)