#This homework is the prim's algorithm. We need to include the node with the shortest distance from the explored set(MST) into the explored set(MST).
#Therefore, heap is the most appropriate data structure
# Something about this heap:
# When insert a node to the heap, we plug in both the data and the number of node.
# But when deletion, we need plug in the position of a nodes.
# There is a distionary "node to position" which is used to find the position.

import random

class node:
    min_distance=0
    node_num=0
    def __init__(self,min_distance,node_num):
        self.min_distance=min_distance
        self.node_num=node_num

class min_heap:
    data=list()
    node_to_position=dict()
    def __init__(self):
        self.data.append(node(0,0))
        self.node_to_position[0]=0
    def print(self):
        for i in range(len(self.data)):
            print(self.data[i].min_distance,self.data[i].node_num)
    def swap(self,position1,position2):
        self.node_to_position[self.data[position1].node_num]=position2
        self.node_to_position[self.data[position2].node_num]=position1
        temp=self.data[position1]
        self.data[position1]=self.data[position2]
        self.data[position2]=temp
    def insert(self,min_distance,node_num):
        self.data.append(node(min_distance,node_num))
        self.node_to_position[node_num]=len(self.data)-1
        self.bubble_up(len(self.data)-1)
    def bubble_up(self,position):
        if position>1:
            up_position=self.half(position)
            if self.data[position].min_distance<self.data[up_position].min_distance:
                self.swap(position,up_position)
                self.bubble_up(up_position)
    def half(self,n):
        if n%2==1:
            return int((n-1)/2)
        else:
            return int(n/2)
    def bubble_down(self,position):
        if 2*position+1<=len(self.data)-1:
            if self.data[2*position].min_distance<self.data[2*position+1].min_distance and self.data[position].min_distance>self.data[2*position].min_distance:
                self.swap(position,2*position)
                self.bubble_down(2*position)
            elif self.data[2*position].min_distance>self.data[2*position+1].min_distance and self.data[position].min_distance>self.data[2*position+1].min_distance:
                self.swap(position,2*position+1)
                self.bubble_down(2*position+1)
        elif 2*position>len(self.data)-1:
            return
        else:
            if self.data[position].min_distance>self.data[2*position].min_distance:
                self.swap(position,2*position)
                self.bubble_down(2*position)
    def delete(self,position):
        last_node=len(self.data)-1
        if position==last_node:
            self.node_to_position.pop(self.data[last_node].node_num)
            self.data.pop(last_node)
            return
        else:
            self.swap(position,last_node)
            self.node_to_position.pop(self.data[last_node].node_num)
            self.data.pop(last_node)
        if position==1:
            self.bubble_down(position)
        elif position>1:
            if self.data[position].min_distance<self.data[self.half(position)].min_distance:
                self.bubble_up(position)
            else:
                self.bubble_down(position)

file=open('edges.txt','r')

adjacent_list=[{} for i in range(501)]

# create a mapping between nearby nodes and their distance
for lines in file:
    temp=lines.split()
    if len(temp)==3:
        adjacent_list[int(temp[0])][int(temp[1])]=int(temp[2])
        adjacent_list[int(temp[1])][int(temp[0])]=int(temp[2])

#print(adjacent_list[500])

heap=min_heap()
length=0
explored_node=[0]*len(adjacent_list)

# initialize the distance of those node still NOT in the MST
for i in range(1,len(adjacent_list)):
    heap.insert(10000,i)

#randomly pick one node as the source node,
source=random.randrange(1,len(adjacent_list))

#Initialize the shortest distances from the nearby nodes
heap.delete(heap.node_to_position[source])
explored_node[source]=1
for i in adjacent_list[source]:
    heap.delete(heap.node_to_position[i])
    heap.insert(adjacent_list[source][i],i)

#since the second round, we run this for loop
for i in range(1,len(adjacent_list)-1):
    source=heap.data[1].node_num              #get the node number currently has the shortest distance
    explored_node[source]=1                   #mark it as to be explored
    length=length+heap.data[1].min_distance   #add its length to the length of MST
    heap.delete(1)                            # delete it from the heap
    for j in adjacent_list[source]:           # for every nearby node not in our MST
        if explored_node[j]==0:
            distance=heap.data[heap.node_to_position[j]].min_distance
            heap.delete(heap.node_to_position[j])             # record their distance, take them out of the heap
            distance=min(distance,adjacent_list[source][j])   # calculate their new shortest distances, then place them back to the heap.
            heap.insert(distance,j)
    print(length)

heap.print()
print(length)

Greedy Algorithms, Minimum Spanning Trees, and Dynamic Programming/3_1/algo3_1_2.py


from multiprocessing import Semaphore
from typing import Callable

class H2O:
    def __init__(self):
        # set hydrogen semaphore to 0
        self.hSem = Semaphore(0)
        # set oxygen semaphore to 2, required to call oxygen once
        self.oSem = Semaphore(2)
        # using a mutual exclusion semaphore so that oxygen can acquire two counts of oSem
        self.mutex = Semaphore(1)

    # releaseHydrogen() outputs "H"

    def hydrogen(self, releaseHydrogen: Callable[[], None]) -> None:
        # calling hyrogen aquires one hydrogen semaphore, and releases one oxygen semaphore
        self.hSem.acquire()
        releaseHydrogen()
        self.oSem.release()

    # releaseOxygen() outputs "O"

    def oxygen(self, releaseOxygen: Callable[[], None]) -> None:
        # calling oxygen requires two hydrogen semaphores, as the oxygen to hydrogen ratio is 1:2
        # acquire and release mutual exclusion semaphore
        with self.mutex:
            self.oSem.acquire()
            self.oSem.acquire()
        releaseOxygen()
        # release two hydrogen semaphores, (release(n) in python 3.9)
        self.hSem.release()
        self.hSem.release()


from jukebox.models import Song, JukeBox, JukeUser, Stats, PlaylistMembership, Playlist
from django.contrib.auth.models import User
u = User.objects.all()[0]
# JukeUser.objects.all()[0]
user = JukeUser(user=u,firstname="Sir",lastname="Dummy")
user.save()
jb = JukeBox(name="DummyJuke",owner=user,is_public=True)
playlist = Playlist()
playlist.save()
stats = Stats()
stats.save()
jb.djs.add(user)
jb.playlist = playlist
jb.stats = stats
jb.save()


brandOnSale = 'Tesla'

class Car:

    def __init__(self, brand, model, isAirBagOK, isPaintingOK, isMechanicalOK, isOnSale):
        self.brand = brand
        self.model = model
        self.isAirBagOK = isAirBagOK
        self.isPaintingOK = isPaintingOK
        self.isMechanicalOK = isMechanicalOK
        self.__isOnSale = isOnSale

    @property
    def IsOnSale(self):
        return self.__isOnSale

    @IsOnSale.setter #ustawiacz aby można było zmieniać poza
    def __SetIsOnSale(self, newIsOnSaleStatus):
        if self.brand == brandOnSale:
            self.__isOnSale = newIsOnSaleStatus
            print('Changing status IsOnSale to {} for {}'.format(newIsOnSaleStatus, self.brand))
        else:
            print('Cannot change status IsOnSale. Sale valid only for {}'.format(brandOnSale))

    @IsOnSale.deleter
    def IsOnSale(self):
        self.__isOnSale = None

    @property
    def CarTitle(self):
        return "Brand {}, Model {}".format(self.brand, self.model).title()

car_01 = Car('BMW', '320D (E91)', True, True, True, False)
car_02 = Car('Tesla','Model S', True, False, True, True)

print(car_01.IsOnSale)

del car_01.IsOnSale

print(car_01.IsOnSale)
print(car_01.CarTitle)


from openpype.lib.profiles_filtering import filter_profiles
from openpype.settings import get_project_settings

def get_versioning_start(
    project_name,
    host_name,
    task_name=None,
    task_type=None,
    family=None,
    subset=None,
    project_settings=None,
):
    """Get anatomy versioning start"""
    if not project_settings:
        project_settings = get_project_settings(project_name)

    version_start = 1
    settings = project_settings["global"]
    profiles = settings.get("version_start_category", {}).get("profiles", [])

    if not profiles:
        return version_start

    filtering_criteria = {
        "host_names": host_name,
        "families": family,
        "task_names": task_name,
        "task_types": task_type,
        "subsets": subset
    }
    profile = filter_profiles(profiles, filtering_criteria)

    if profile is None:
        return version_start

    return profile["version_start"]


import sqlite3

from flask import Flask
from opencensus.ext.flask.flask_middleware import FlaskMiddleware

from opentelemetry import trace
from opentelemetry.exporter.jaeger.thrift import JaegerExporter
from opentelemetry.instrumentation.sqlite3 import SQLite3Instrumentor
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.shim.opencensus import install_shim

DB = "example.db"

# Set up OpenTelemetry
tracer_provider = TracerProvider(
    resource=Resource(
        {
            "service.name": "opencensus-shim-example-flask",
        }
    )
)
trace.set_tracer_provider(tracer_provider)

# Configure OTel to export traces to Jaeger
tracer_provider.add_span_processor(
    BatchSpanProcessor(
        JaegerExporter(
            agent_host_name="localhost",
            agent_port=6831,
        )
    )
)
tracer = tracer_provider.get_tracer(__name__)

# Install the shim to start bridging spans from OpenCensus to OpenTelemetry
install_shim()

# Instrument sqlite3 library
SQLite3Instrumentor().instrument()

# Setup Flask with OpenCensus instrumentation
app = Flask(__name__)
FlaskMiddleware(app)

# Setup the application database
def setup_db():
    with sqlite3.connect(DB) as con:
        cur = con.cursor()
        cur.execute(
            """
            CREATE TABLE IF NOT EXISTS movie(
                title,
                year,
                PRIMARY KEY(title, year)
            )
            """
        )
        cur.execute(
            """
            INSERT OR IGNORE INTO movie(title, year) VALUES
                ('Mission Telemetry', 2000),
                ('Observing the World', 2010),
                ('The Tracer', 1999),
                ('The Instrument', 2020)
            """
        )

setup_db()

@app.route("/")
def hello_world():
    lines = []
    with tracer.start_as_current_span("query movies from db"), sqlite3.connect(
        DB
    ) as con:
        cur = con.cursor()
        for title, year in cur.execute("SELECT title, year from movie"):
            lines.append(f"
{title} is from the year {year}
")

    with tracer.start_as_current_span("build response html"):
        html = f"
{''.join(lines)}
"

    return html


import logging

from .exceptions import DuplicateReferenceException, UnknownReferenceException

logger = logging.getLogger(__name__)

# Some references are not found in the dump file
MAGIC_REFERENCES = ("sound",)

def convert_value(value):
    # Convert values from postgresql format to python values
    if value == "true":
        return True
    elif value == "false":
        return False
    else:
        return value

class ImportSet:
    def __init__(self, raw_data, data_definition):
        self._data = raw_data
        self._data_definition = data_definition
        self._resources = {}
        for resource_type in data_definition.keys():
            self._resources[resource_type] = []
        self._referenced_resources = {}

        self._build_resources()
        self._build_references()

    def _build_resources(self):
        logger.info("building all resources")
        for type_, content in self._data.items():
            if not content:
                continue

            headers = content[0]
            for row in content[1:]:
                resource = {k: convert_value(v) for k, v in zip(headers, row)}
                resource["type_"] = type_
                if type_ not in self._resources:
                    self._resources[type_] = []
                logger.debug("resource built : %s", resource)
                self._resources[type_].append(resource)
        logger.debug("resource building done")

    def _build_references(self):
        logger.info("building references")
        for resources in self._resources.values():
            if not resources:
                continue  # There's no resources for this type

            headers = list(resources[0].keys())
            if "ref" not in headers:
                continue  # The resources in this list have no "ref" column

            for resource in resources:
                reference = resource["ref"]
                if not reference:
                    continue
                logger.debug("reference built : %s", reference)
                if reference in self._referenced_resources:
                    raise DuplicateReferenceException(reference)

                self._referenced_resources[reference] = resource
        logger.debug("reference building done")

    def check_references(self):
        logger.info("checking references")
        unknown_references = set()

        for type_, resources in self._resources.items():
            if not resources:
                continue

            references = self._data_definition.get(type_, {}).get("references", [])
            for reference in references:
                for resource in resources:
                    lookedup_reference = resource.get(reference)
                    if not lookedup_reference:
                        continue
                    if lookedup_reference in MAGIC_REFERENCES:
                        continue
                    if lookedup_reference not in self._referenced_resources:
                        unknown_references.add(lookedup_reference)

        logger.debug("reference checking done")

        if unknown_references:
            raise UnknownReferenceException(list(unknown_references))

    def list(self, resource_type):
        return self._resources.get(resource_type, [])

    def get_resource(self, reference):
        return self._referenced_resources.get(reference)


# ID - 10984191
# DEPARTMENT - BIOMEDICAL ENGINEERING

import qrcode
import PySimpleGUI as sg

sg.theme('BlueMono')
font = ('Monaco', 18)

qr_image=[sg.Image('',key='-QRCODE-',size=(250,250),background_color='grey')]

layout=[
    [sg.Text('Enter URL:')],
    [sg.Input('',key='-URL-',justification='center')],
    [sg.Button('Create',key='-submit-',expand_x=True,button_color='red')],
    [sg.Column([qr_image],justification='center')],
]

wind = sg.Window('QRCODE Generator App',layout, font=font)

while True:
    event,values = wind.read()
    if event==sg.WIN_CLOSED:
        break
    elif event == '-submit-':
        url = values['-URL-']
        if url:
            image= qrcode.make(url)
            image.save('qr.png')
            wind['-QRCODE-'].update('qr.png')
wind.close()


from databaser.db_parser.table_data.condition_parser import ConditionParser

class Update:
    def __init__(self, table_name: str, data: dict, conditions: dict, table_quote: str = "", field_quote: str = "", value_quote: bool = False, schema_name: str = "public"):
        self.table_name = table_name
        self.schema_name = schema_name
        self.conditions = conditions
        self.data = data

        self.table_quote = table_quote
        self.field_quote = field_quote
        self.value_quote = "'" if value_quote else ""

    def get_sql(self):
        data = []
        where = "" if len(self.conditions.keys()) == 0 else ConditionParser(self.conditions, self.field_quote).get_parsed()

        for field in self.data.keys():
            value = f"{self.field_quote}{field}{self.field_quote} = '{self.data[field]}'"
            data.append(value)

        if len(data) == 0:
            return ""

        if where != "":
            where = "WHERE " + where

        return f"""UPDATE {f"{self.table_quote}{self.schema_name}{self.table_quote}." if self.schema_name != '' else ""}{self.table_quote}{self.table_name}{self.table_quote} SET {f', '.join(data)} {where};"""


#!usr/bin/python2

import socket
recv_ip="127.0.0.1"
recv_port=4444  # 0-1024 to check netstst -nulp/ntlp

# creating udp socket
#                  ipv4             udp
s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
#fitting ip and port with udp socket
s.bind((recv_ip,recv_port))

#recv data from sender
data=s.recvfrom(50)
print(data)


N, M = map(int, input().split())
grid = []
homes = []
chickens = []
distances = []

def distance(start, end):
    return abs(start[0]-end[0]) + abs(start[1]-end[1])

for y in range(N):
    line = list(map(int, input().split()))
    for x in range(N):
        if line[x] == 1:
            homes.append((x, y))
        elif line[x] == 2:
            chickens.append((x, y))
            distances.append([0, (x, y)])
    grid.append(line)

for h in range(len(homes)):
    for c in range(len(chickens)):
        distances[c][0] += distance(homes[h], chickens[c])

distances.sort(key=lambda x:x[0])
s = 0

for h in range(len(homes)):
    minimum = 200000000
    for i in range(M):
        minimum = min(minimum, distance(homes[h], distances[i][1]))
    s += minimum

print(s)
"""
def bfs(start):
    q = Queue()
    visit = [ [ False for _ in range(N)] for _ in range(N)]
    q.put(start)

    find_y = -1
    find_x = -1

    while q.qsize() > 0 and find_y == -1:
        cur = q.get()
        visit[cur[1]][cur[0]] = True
        for d in direction:
            nx = cur[0] + d[0]
            ny = cur[1] + d[1]

            if nx < 0 or nx >= N or ny < 0 or ny >= N or visit[ny][nx]:
                continue

            if grid[ny][nx] == 2:
                find_y = ny
                find_x = nx
                break

            q.put((nx, ny))

    return abs(start[0]-find_x) + abs(start[1]-find_y)

while len(homes) > 0:
    home = homes.pop()
    bfs(home)

dist = reduce(lambda x,y : x+y, distances)
dist.sort()

s = 0
for i in range(M):
    s += distances[i]

print(s)
"""


import pandas as pd
import nltk
import os
from output_folder import get_output_file, get_output_folder

STEMMING = False
LEMMING =  True

OUTPUT_PATH = os.getenv('OUTPUT_PATH')
OUT_FOLDER = get_output_folder('so_data/')
PROCESSED_CSV = get_output_file('processed/so_questions.csv')

# Utility functions
w_tokenizer = nltk.tokenize.WhitespaceTokenizer()
lemmatizer = nltk.stem.WordNetLemmatizer()
stemmer = nltk.stem.snowball.SnowballStemmer("english")

def lemmatize_text(text):
    return [lemmatizer.lemmatize(w) for w in w_tokenizer.tokenize(text)]

def stem_text(text):
    return [stemmer.stem(w) for w in w_tokenizer.tokenize(text)]

def df_to_file(_index, _row, _fieldname):
    issue_name = str(int(_row['Id']))
    issue_body = str(_row[_fieldname])

    if STEMMING:
        issue_body = ' '.join(stem_text(issue_body))
    if LEMMING:
        issue_body = ' '.join(lemmatize_text(issue_body))
    with open(OUT_FOLDER + issue_name + '.txt', 'a+') as issue_file:
        issue_file.write(issue_body + '\n')

def export_to_mallet():
  # load stack overflow questions
  so = pd.read_csv(PROCESSED_CSV)

  # output one question per file as mallet requires (title only)
  for index, row in so.iterrows():
    df_to_file(index, row, 'Title')

  # output comments one question per file as mallet requires (body only)
  for index, row in so.iterrows():
    df_to_file(index, row, 'Body')

if __name__ == '__main__':
  export_to_mallet()


from __future__ import print_function

import tensorflow as tf
import random
import numpy as np

def prepare_for_sampling(elem):
    elem['sampled_occ'] = elem['known_occ'] + 0.0
    known = elem['known_occ'] + elem['known_free']
    rough_estimate = elem['gt_occ']
    elem['conditioned_occ'] = elem['known_occ'] + (1-known) * rough_estimate
    # elem['conditioned_occ'] = elem['known_occ'] + 0.0
    return elem

def update_from_sampling(elem, sampled_ind, sampled_val):
    """
    Updates elem based on the sampled ind and sampled val. Returns (elem, bool) indicating if a new inference pass is needed
    """
    elem['sampled_occ'][sampled_ind] = sampled_val
    update_needed = elem['conditioned_occ'][sampled_ind] != sampled_val

    if update_needed:
        elem['conditioned_occ'][sampled_ind] = sampled_val
        # elem['conditioned_occ'][sampled_ind] = elem['gt_occ'][sampled_ind]

    return elem, update_needed

class EfficientCNNSampler:
    def __init__(self, elem):
        shape = elem['known_free'].shape[1:4]
        self.indices = [(i,j,k) for i in range(shape[0]) for j in range(shape[1]) for k in range(shape[2])]
        self.indices_iter = self.indices.__iter__()
        self.pred_free = None
        self.pred_occ = None
        self.ct = 0

    def sample(self, model, elem, inference):
        """
        Draws a sample and updates the inference only if necessary
        """

        if self.pred_free is None:
            self.pred_free = inference['predicted_free'].numpy()
            self.pred_occ = inference['predicted_occ'].numpy()

        ind = next(self.indices_iter)
        i = (0,ind[0],ind[1],ind[2],0)

        p = self.pred_occ[i]

        if elem['known_occ'][i] == 1.0 or elem['known_free'][i] == 1.0:
            return elem, inference

        # if p > random.random():
        if p > 0.5:
            # IPython.embed()
            elem, need_inference = update_from_sampling(elem, i, 1.0)
        else:
            elem, need_inference = update_from_sampling(elem, i, 0.0)

        if need_inference:
            self.ct += 1
            if self.ct % 100 == 0:
                print("Performing inference number {}: ({}, {}, {})".format(self.ct, ind[0], ind[1], ind[2]))

            inference = model.model(elem)
            self.pred_free = inference['predicted_free'].numpy()
            self.pred_occ = inference['predicted_occ'].numpy()

        return elem, inference

        # if elem['known_free'][i] == 1.0:
        #     return elem, inference
        # if elem['known_occ'][i] == 1.0:
        #     return elem, inference

        # if self.pred_free[i] > 1.0:
        #     elem['known_free'][i]=1.0
        #     return elem, inference
        # if self.pred_occ[i] > 1.0:
        #     elem['known_occ'][i]=1.0
        #     return elem, inference

        # return elem, inference

class UnknownSpaceSampler:
    def __init__(self, elem):
        shape = elem['known_free'].shape[1:4]
        self.indices = [(i,j,k) for i in range(shape[0]) for j in range(shape[1]) for k in range(shape[2])]
        # random.shuffle(self.indices)
        self.indices_iter = self.indices.__iter__()
        self.pred_free = None
        self.pred_occ = None
        self.ct = 0

    def sample(self, model, elem, inference):

        if self.pred_free is None:
            self.pred_free = inference['predicted_free'].numpy()
            self.pred_occ = inference['predicted_occ'].numpy()

        # for ind in indices:
        ind = next(self.indices_iter)
        i = (0,ind[0],ind[1],ind[2],0)

        if elem['known_free'][i] == 1.0:
            return elem, inference
        if elem['known_occ'][i] == 1.0:
            return elem, inference

        inference = model.model(elem)
        self.pred_free = inference['predicted_free'].numpy()
        self.pred_occ = inference['predicted_occ'].numpy()

        if self.pred_free[i] > 1.0:
            elem['known_free'][i]=1.0
            return elem, inference
        if self.pred_occ[i] > 1.0:
            elem['known_occ'][i]=1.0
            return elem, inference

        p = self.pred_occ[i]

        # if p > random.random():
        if p > 0.5:
            elem['known_occ'][i] = 1.0
        else:
            elem['known_free'][i] = 1.0

        self.ct += 1
        print("Sampling {}: ({}, {}, {})".format(self.ct, ind[0], ind[1], ind[2]))

        return elem, inference

class MostConfidentSampler:
    def __init__(self, elem):
        self.shape = elem['known_free'].shape
        self.ct = 0

    def sample(self, model, elem, inference):
        po = inference['predicted_occ']
        pf = inference['predicted_free']

        unknown = 1 - (elem['known_free'] + elem['known_occ'])
        if tf.math.reduce_sum(unknown) <= 0:
            raise StopIteration

        c = tf.math.reduce_max(tf.abs(po - pf) * unknown)
        c = tf.math.minimum(c, 1.0)

        mask = tf.abs(po-pf) * unknown >= c
        sampled_occ = tf.random.uniform(shape=self.shape, minval=-.99, maxval=1.0) < (po-pf)
        sampled_free = tf.logical_not(sampled_occ)

        new_occ = tf.cast(tf.logical_and(mask, sampled_occ), tf.float32)
        new_free = tf.cast(tf.logical_and(mask, sampled_free), tf.float32)

        elem['known_occ'] += new_occ.numpy()
        elem['known_free'] += new_free.numpy()

        inference = model.model(elem)
        self.ct += 1

        new_known_ct = tf.reduce_sum(new_occ + new_free)

        print("Sampling iteration {}: threshold {}, new {}, still unknown {}".\
              format(self.ct, c, new_known_ct, tf.math.reduce_sum(unknown)))

        # IPython.embed()

        return elem, inference


from functools import partial
import pickle
from graphein.protein.config import ProteinGraphConfig
from graphein.protein.graphs import construct_graph
from graphein.protein.features.sequence import compute_esm_embedding
from graphein.protein.features.sequence.utils import (
    subset_by_node_feature_value,
)
from graphein.protein.features.nodes.amino_acid import (
    amino_acid_one_hot,
    meiler_embedding,
    expasy_protein_scale,
    hydrogen_bond_acceptor,
    hydrogen_bond_donor,
)
from graphein.protein.edges.distance import (
    add_distance_threshold,
    add_peptide_bonds,
    add_hydrophobic_interactions,
    add_hydrogen_bond_interactions,
    add_disulfide_interactions,
    add_ionic_interactions,
    add_aromatic_interactions,
    add_aromatic_sulphur_interactions,
    add_cation_pi_interactions,
)
from graphein.ml import GraphFormatConvertor
import graphein
import networkx as nx
import numpy as np
import torch
import freesasa
graphein.verbose(enabled=False)

def load_protein_as_graph(pdb_file, output_file=None):
    """
    Loads a protein chain PDB file as a Graphein networkx graph.

    Parameters
    ----------
    pdb_file : str
        Path to PDB file.
    output_file : str, optional
        Path to output file to save graph as pickle, by default None

    Returns
    -------
    nx.Graph
    """
    config = ProteinGraphConfig(
        node_metadata_functions= [
            amino_acid_one_hot,
            meiler_embedding,
            expasy_protein_scale,
            hydrogen_bond_acceptor,
            hydrogen_bond_donor,
        ],
        edge_construction_functions=[       # List of functions to call to construct edges.
            add_peptide_bonds,
            add_hydrophobic_interactions,
            add_aromatic_interactions,
            add_disulfide_interactions,
            add_ionic_interactions,
            add_hydrogen_bond_interactions,
            add_aromatic_sulphur_interactions,
            add_cation_pi_interactions,
            partial(add_distance_threshold, long_interaction_threshold=5, threshold=10.),
        ],
        graph_metadata_functions=[
                             esm_residue_embedding,
                             partial(solvent_accessible_surface_area, pdb_file=pdb_file)
                             ]
    )

    graph = construct_graph(config=config, path=pdb_file, verbose=False)
    if output_file is not None:
        with open(output_file, "wb") as f:
            pickle.dump(graph, f)
    return graph

def graphein_to_pytorch_graph(graphein_graph, node_attr_columns: list, edge_attr_columns: list, edge_kinds: set, labels):
    """
    Converts a Graphein graph to a pytorch-geometric Data object.
    """
    if edge_attr_columns is None:
        edge_attr_columns = []
    columns = [
            "chain_id",
            "coords",
            "edge_index",
            "kind",
            "node_id",
            "residue_number",
        ] + node_attr_columns + edge_attr_columns
    convertor = GraphFormatConvertor(src_format="nx", dst_format="pyg", columns=columns, verbose = None)
    data = convertor(graphein_graph)
    data_dict= data.to_dict()
    x_data = []
    for x in node_attr_columns:
        if data_dict[x].ndim == 1:
            x_data.append(torch.atleast_2d(data_dict[x]).T)
        else:
            x_data.append(torch.atleast_2d(data_dict[x]))
    data.x = torch.hstack(x_data).float()
    if len(edge_attr_columns) > 0:
        edge_attr_data = []
        for x in edge_attr_columns:
            if data_dict[x].ndim == 1:
                edge_attr_data.append(torch.atleast_2d(data_dict[x]).T)
            else:
                edge_attr_data.append(torch.atleast_2d(data_dict[x]))
        data.edge_attr = torch.hstack(edge_attr_data).float()
    # prune edge_index and edge_attr based on edge_kinds
    edge_index = []
    edge_attr = []
    for i, kind in enumerate(data_dict["kind"]):
        if len(kind.intersection(edge_kinds)) > 0:
            edge_index.append([data.edge_index[0, i], data.edge_index[1, i]])
            if len(edge_attr_columns) > 0:
                edge_attr.append(data.edge_attr[i])
    data.edge_index = torch.tensor(edge_index).T.long()
    if len(edge_attr_columns) > 0:
        data.edge_attr = torch.tensor(edge_attr).float()
    data.pos = data.coords.float()
    data.y = torch.zeros(data.num_nodes)
    for i, res_num in enumerate(data.residue_number):
        if f"{data.chain_id[i]}_{res_num.item()}" in labels:
            data.y[i] = 1
    return data

def solvent_accessible_surface_area(G: nx.Graph, pdb_file) -> nx.Graph:
    #Add SASA as a node feature using freesasa
    structure = freesasa.Structure(str(pdb_file))
    result = freesasa.calc(structure)

    for node, data in G.nodes(data=True):
        chain_id = data['chain_id']
        residue_number = str(data['residue_number'])

        # Calculate the SASA for each node/residue
        G.nodes[node]['sasa'] = 0
        G.nodes[node]['rel_sasa'] = 0
        try:
            G.nodes[node]['sasa'] = result.residueAreas()[chain_id][residue_number].total
            G.nodes[node]['rel_sasa'] = result.residueAreas()[chain_id][residue_number].relativeTotal
        except KeyError:
            pass
    return G

def esm_residue_embedding(
    G: nx.Graph,
    model_name: str = "esm1b_t33_650M_UR50S",
    output_layer: int = 33,
) -> nx.Graph:
    """
    Computes ESM residue embeddings from a protein sequence and adds the to the
    graph.

        *Biological Structure and Function Emerge from Scaling Unsupervised*
        *Learning to 250 Million Protein Sequences* (2019)
        Rives, Alexander and Meier, Joshua and Sercu, Tom and Goyal, Siddharth
        and Lin, Zeming and Liu, Jason and Guo,
        Demi and Ott, Myle and Zitnick, C. Lawrence and Ma, Jerry and Fergus,
        Rob

        *Transformer protein language models are unsupervised structure learners*
        (2020) Rao, Roshan M and Meier, Joshua and Sercu, Tom and Ovchinnikov,
        Sergey and Rives, Alexander

    **Pre-trained models**

    =========                     ====== ====== ======= ============= =========
    Full Name                     layers params Dataset Embedding Dim Model URL
    =========                     ====== ====== ======= ============= =========
    ESM-1b esm1b_t33_650M_UR50S   33    650M   UR50/S     1280        https://dl.fbaipublicfiles.com/fair-esm/models/esm1b_t33_650M_UR50S.pt
    ESM1-main esm1_t34_670M_UR50S 34    670M   UR50/S     1280        https://dl.fbaipublicfiles.com/fair-esm/models/esm1_t34_670M_UR50S.pt
    esm1_t34_670M_UR50D           34    670M   UR50/D     1280        https://dl.fbaipublicfiles.com/fair-esm/models/esm1_t34_670M_UR50D.pt
    esm1_t34_670M_UR100           34    670M   UR100      1280        https://dl.fbaipublicfiles.com/fair-esm/models/esm1_t34_670M_UR100.pt
    esm1_t12_85M_UR50S            12    85M    UR50/S     768         https://dl.fbaipublicfiles.com/fair-esm/models/esm1_t12_85M_UR50S.pt
    esm1_t6_43M_UR50S             6     43M    UR50/S     768         https://dl.fbaipublicfiles.com/fair-esm/models/esm1_t6_43M_UR50S.pt
    =========                     ====== ====== ======= ============= =========

    :param G: ``nx.Graph`` to add esm embedding to.
    :type G: nx.Graph
    :param model_name: Name of pre-trained model to use.
    :type model_name: str
    :param output_layer: index of output layer in pre-trained model.
    :type output_layer: int
    :return: ``nx.Graph`` with esm embedding feature added to nodes.
    :rtype: nx.Graph
    """

    for chain in G.graph["chain_ids"]:
        sequence = G.graph[f"sequence_{chain}"]
        embeddings = []
        for i in range(0, len(sequence), 1024): #TODO: improve chunking using overlap
            partial_embedding = compute_esm_embedding(
                sequence[i : i + 1022],
                representation="residue",
                model_name=model_name,
                output_layer=output_layer,
            )
            embeddings.append(partial_embedding[0, 1:-1])
        embedding = np.concatenate(embeddings, axis=0)
        assert len(embedding) == len(sequence)
        subgraph = subset_by_node_feature_value(G, "chain_id", chain)

        for i, (n, d) in enumerate(subgraph.nodes(data=True)):
            G.nodes[n]["esm_embedding"] = embedding[i]

    return G


import pytest

from aiohttp.test_utils import TestClient
from pytest_mock import MockerFixture

from ahriman.models.build_status import BuildStatusEnum
from ahriman.models.package import Package
from ahriman.models.user_access import UserAccess
from ahriman.web.views.v1.status.packages import (PackagesView)

async def test_get_permission() -> None:
    """
    must return correct permission for the request
    """
    for method in ("GET",):
        request = pytest.helpers.request("", "", method)
        assert await PackagesView.get_permission(request) == UserAccess.Read
    for method in ("POST",):
        request = pytest.helpers.request("", "", method)
        assert await PackagesView.get_permission(request) == UserAccess.Full

def test_routes() -> None:
    """
    must return correct routes
    """
    assert PackagesView.ROUTES == ["/api/v1/packages"]

async def test_get(client: TestClient, package_ahriman: Package, package_python_schedule: Package) -> None:
    """
    must return status for all packages
    """
    await client.post(f"/api/v1/packages/{package_ahriman.base}",
                      json={"status": BuildStatusEnum.Success.value, "package": package_ahriman.view()})
    await client.post(f"/api/v1/packages/{package_python_schedule.base}",
                      json={"status": BuildStatusEnum.Success.value, "package": package_python_schedule.view()})
    response_schema = pytest.helpers.schema_response(PackagesView.get)

    response = await client.get("/api/v1/packages")
    assert response.status == 200
    json = await response.json()
    assert not response_schema.validate(json, many=True)

    packages = [Package.from_json(item["package"]) for item in json]
    assert packages
    assert {package.base for package in packages} == {package_ahriman.base, package_python_schedule.base}

async def test_get_with_pagination(client: TestClient, package_ahriman: Package,
                                   package_python_schedule: Package) -> None:
    """
    must return paginated status for packages
    """
    await client.post(f"/api/v1/packages/{package_ahriman.base}",
                      json={"status": BuildStatusEnum.Success.value, "package": package_ahriman.view()})
    await client.post(f"/api/v1/packages/{package_python_schedule.base}",
                      json={"status": BuildStatusEnum.Success.value, "package": package_python_schedule.view()})
    request_schema = pytest.helpers.schema_request(PackagesView.get, location="querystring")
    response_schema = pytest.helpers.schema_response(PackagesView.get)

    payload = {"limit": 1, "offset": 1}
    assert not request_schema.validate(payload)
    response = await client.get("/api/v1/packages", params=payload)
    assert response.status == 200
    json = await response.json()
    assert not response_schema.validate(json, many=True)

    packages = [Package.from_json(item["package"]) for item in json]
    assert packages
    assert {package.base for package in packages} == {package_python_schedule.base}

async def test_post(client: TestClient, mocker: MockerFixture) -> None:
    """
    must be able to reload packages
    """
    load_mock = mocker.patch("ahriman.core.status.watcher.Watcher.load")

    response = await client.post("/api/v1/packages")
    assert response.status == 204
    load_mock.assert_called_once_with()


"""
0345. Reverse Vowels of a String (Easy)
https://leetcode.com/problems/reverse-vowels-of-a-string/
"""

class Solution:
    def reverseVowels(self, s: str) -> str:
        vowels = ["a", "e", "i", "o", "u", "A", "E", "I", "O", "U"]
        left = 0
        right = len(s)-1
        s_list = list(s)
        while left < right:
            if s_list[left] not in vowels:
                left += 1
                continue
            if s_list[right] not in vowels:
                right -= 1
                continue
            s_list[left], s_list[right] = s_list[right], s_list[left]
            left += 1
            right -= 1
        return "".join(s_list)

def main():
    s = Solution()
    strs = "hello"
    print(f"Input: {strs} \nOutput: {s.reverseVowels(strs)}")
    strs = "leetcode"
    print(f"Input: {strs} \nOutput: {s.reverseVowels(strs)}")

if __name__ == "__main__":
    main()


import pickle
import random
from collections import defaultdict
import math
from datetime import datetime, timedelta

import geopy
from geopy import distance
from geopy.geocoders import Nominatim

# use your own user agent
geolocator = Nominatim(user_agent="bfabiandev")

DAYS = 177
DISTANCES = dict()

# Class to represent teams, which contains the basic information (name, location, conference, division) and its schedule
# Also contains the weight for penalties that define what sort of schedule we get in the end
class Team:
    def __init__(self, name, location, conference, division):
        self.name = name
        self.loc = location
        self.conf = conference
        self.div = division
        self.schedule = defaultdict(set)

    def __str__(self):
        return self.name

    def add_game(self, game):
        self.schedule[game.date].add(game)

    def remove_game(self, game):
        self.schedule[game.date].remove(game)

    def loss(self):
        loss = 0

        current_home_team = self
        days_rest = 100
        road_trip_length = 0

        for i in range(1, DAYS+1):
            days_rest += 1
            if len(self.schedule[i]) == 0:
                continue
            else:
                if len(self.schedule[i]) > 1:
                    loss += 1e10 * len(self.schedule[i])

                game = next(iter(self.schedule[i]))
                next_home_team = game.home

                # get penalty based on distance flown
                dist = dist_teams(current_home_team, next_home_team)
                current_home_team = next_home_team
                loss += dist * 0.05

                # get penalty for back-to-backs
                if days_rest == 1:
                    rest_penalty = 200
                elif days_rest == 2:
                    rest_penalty = 50
                else:
                    rest_penalty = 0
                loss += rest_penalty

                # get penalty for long road-trips
                if game.home == self:
                    road_trip_length = 0
                else:
                    road_trip_length += 1

                if road_trip_length > 2:
                    road_trip_penalty = 10 * road_trip_length
                else:
                    road_trip_penalty = 0

                loss += road_trip_penalty

                days_rest = 0

        return loss

    def number_of_home_games(self):
        return sum([1 for date in self.schedule for game in self.schedule[date] if game.home == self])

    def number_of_back_to_backs(self):
        back_to_backs = 0
        for date1 in range(1, DAYS):
            if len(self.schedule[date1]) > 0 and len(self.schedule[date1 + 1]) > 0:
                back_to_backs += 1
        return back_to_backs

    def number_of_back_to_backs_to_backs(self):
        back_to_backs_to_backs = 0
        for date1 in range(1, DAYS-1):
            if len(self.schedule[date1]) > 0 and len(self.schedule[date1 + 1]) > 0 and len(self.schedule[date1 + 2]) > 0:
                back_to_backs_to_backs += 1
        return back_to_backs_to_backs

    def __eq__(self, other):
        return isinstance(other, Team) and self.__hash__() == other.__hash__()

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

# Class for representing a game between two teams
class Game:
    def __init__(self, home, away, date=-1):
        self.home = home
        self.away = away
        if date == -1:
            self.date = random.randint(1, DAYS)

    def __str__(self):
        return self.away.__str__() + " @ " + self.home.__str__()

    def __hash__(self):
        return hash((self.home, self.away, self.date))

    def __eq__(self, other):
        return isinstance(other, Game) and self.__hash__() == other.__hash__()

def calculate_distances(teams):
    for i, team1 in enumerate(teams):
        for j, team2 in enumerate(teams):
            if i < j:
                DISTANCES[(team1, team2)] = DISTANCES[(team2, team1)
                                                      ] = calculate_distance(team1, team2)

def calculate_distance(team1, team2):
    loc1 = (team1.loc.latitude, team1.loc.longitude)
    loc2 = (team2.loc.latitude, team2.loc.longitude)

    return distance.distance(loc1, loc2).km

def dist_teams(team1, team2):
    if team1 == team2:
        return 0
    if (team1, team2) in DISTANCES:
        return DISTANCES[(team1, team2)]
    else:
        raise ValueError("Distances have not been precomputed...")

def set_up_schedule():
    try:
        teams = pickle.load(open('teams.pkl', 'rb'))
    except FileNotFoundError:
        teams = create_teams()
        pickle.dump(teams, open('teams.pkl', 'wb'))

    games = add_games(teams)

    return teams, games

# Creating teams, querying location, hardcoded
def create_teams():
    teams = []

    teams.append(Team("Atlanta Hawks", get_first_city(
        'Atlanta'), 'Eastern', 'Southeast'))
    teams.append(Team("Boston Celtics", get_first_city(
        'Boston'), 'Eastern', 'Atlantic'))
    teams.append(Team("Brooklyn Nets", get_first_city(
        'Brooklyn'), 'Eastern', 'Atlantic'))
    teams.append(Team("Charlotte Hornets", get_first_city(
        'Charlotte, NC'), 'Eastern', 'Southeast'))
    teams.append(Team("Chicago Bulls", get_first_city(
        'Chicago'), 'Eastern', 'Central'))
    teams.append(Team("Cleveland Cavaliers", get_first_city(
        'Cleveland'), 'Eastern', 'Central'))
    teams.append(Team("Dallas Mavericks", get_first_city(
        'Dallas'), 'Western', 'Southwest'))
    teams.append(Team("Denver Nuggets", get_first_city(
        'Denver'), 'Western', 'Northwest'))
    teams.append(Team("Detroit Pistons", get_first_city(
        'Detroit'), 'Eastern', 'Central'))
    teams.append(Team("Golden State Warriors", get_first_city(
        'San Francisco'), 'Western', 'Pacific'))
    teams.append(Team("Houston Rockets", get_first_city(
        'Houston'), 'Western', 'Southwest'))
    teams.append(Team("Indiana Pacers", get_first_city(
        'Indianapolis'), 'Eastern', 'Central'))
    teams.append(Team("LA Clippers", get_first_city(
        'Los Angeles'), 'Western', 'Pacific'))
    teams.append(Team("LA Lakers", get_first_city(
        'Los Angeles'), 'Western', 'Pacific'))
    teams.append(Team("Memphis Grizzlies", get_first_city(
        'Memphis'), 'Western', 'Southwest'))
    teams.append(Team("Miami Heat", get_first_city(
        'Miami'), 'Eastern', 'Southeast'))
    teams.append(Team("Milwaukee Bucks", get_first_city(
        'Milwaukee'), 'Eastern', 'Central'))
    teams.append(Team("Minnesota Timberwolves", get_first_city(
        'Minneapolis'), 'Western', 'Northwest'))
    teams.append(Team("New Orleans Pelicans", get_first_city(
        'New Orleans'), 'Western', 'Southwest'))
    teams.append(Team("New York Knicks", get_first_city(
        'New York'), 'Eastern', 'Atlantic'))
    teams.append(Team("Oklahoma City Thunder", get_first_city(
        'Oklahoma City'), 'Western', 'Northwest'))
    teams.append(Team("Orlando Magic", get_first_city(
        'Orlando'), 'Eastern', 'Southeast'))
    teams.append(Team("Philadelphia 76ers", get_first_city(
        'Philadelphia'), 'Eastern', 'Atlantic'))
    teams.append(Team("Phoenix Suns", get_first_city(
        'Phoenix'), 'Western', 'Pacific'))
    teams.append(Team("Portland Trail Blazers", get_first_city(
        'Portland, OR'), 'Western', 'Northwest'))
    teams.append(Team("Sacramento Kings", get_first_city(
        'Sacramento, CA, USA'), 'Western', 'Pacific'))
    teams.append(Team("San Antonio Spurs", get_first_city(
        'San Antonio'), 'Western', 'Southwest'))
    teams.append(Team("Toronto Raptors", get_first_city(
        'Toronto'), 'Eastern', 'Atlantic'))
    teams.append(Team("Utah Jazz", get_first_city(
        'Salt Lake City'), 'Western', 'Northwest'))
    teams.append(Team("Washington Wizards", get_first_city(
        'Washington'), 'Eastern', 'Southeast'))

    return teams

def add_games(teams):
    not_play_4th_time = fill_not_play_4th_time(teams)

    games = set()
    for i, team1 in enumerate(teams):
        for j, team2 in enumerate(teams):
            if i < j:
                if team1.conf != team2.conf:
                    add_game(Game(team1, team2), games)
                    add_game(Game(team2, team1), games)

                elif team1.div == team2.div:
                    add_game(Game(team1, team2), games)
                    add_game(Game(team2, team1), games)
                    add_game(Game(team1, team2), games)
                    add_game(Game(team2, team1), games)
                else:
                    add_game(Game(team1, team2), games)
                    add_game(Game(team2, team1), games)
                    if team2 not in not_play_4th_time[team1] and team1 not in not_play_4th_time[team2]:
                        add_game(Game(team1, team2), games)
                        add_game(Game(team2, team1), games)
                    elif team2 in not_play_4th_time[team1]:
                        add_game(Game(team1, team2), games)
                    elif team1 in not_play_4th_time[team2]:
                        add_game(Game(team2, team1), games)
                    else:
                        raise ValueError("You should never get here")

    return games

def add_game(game, games):
    games.add(game)
    game.home.add_game(game)
    game.away.add_game(game)

def get_first_city(name):
    for i in range(1000):
        try:
            return geolocator.geocode(name, timeout=10)
        except geopy.exc.GeocoderTimedOut:
            print('GeoPy service timeout {}: trying again...'.format(i+1))
            pass
    raise geopy.exc.GeocoderTimedOut

def fill_not_play_4th_time(teams):
    div_opps = defaultdict(set)
    divs = defaultdict(list)
    for team1 in teams:
        divs[team1.div].append(team1)

    for i, team1 in enumerate(teams):
        for j, team2 in enumerate(teams):
            if i < j:
                if team1.conf == team2.conf and team1.div != team2.div:
                    div_opps[team1.div].add(team2.div)

    not_play_4th_time = defaultdict(list)

    for i, div in enumerate(divs):
        for j, div2 in enumerate(divs):
            if i < j and div in div_opps[div2]:
                for idx in range(5):
                    not_play_4th_time[divs[div][idx]].append(
                        divs[div2][idx % 5])
                    not_play_4th_time[divs[div2]
                                      [(idx+1) % 5]].append(divs[div][idx])

    return not_play_4th_time

##########################
### UTILS FOR PRINTING ###
##########################

def convert_day_to_date(days):
    start_date = datetime.strptime("10/16/18", "%m/%d/%y")
    return start_date + timedelta(days=days-1)

def save_schedule_to_textfile(teams, games):
    with open('schedule.txt', 'w') as f:
        for team in teams:
            print(team, file=f)
            for day in sorted(list(team.schedule.keys())):
                if len(team.schedule[day]) > 0:
                    print("{}: ".format(convert_day_to_date(
                        day).strftime('%Y-%m-%d')), end='', file=f)
                    for game in team.schedule[day]:
                        print(game, file=f)

            print("", file=f)

        print("Number of games: ", len(games), file=f)

############################################################
### DEFINING ACTIONS THAT CAN BE TAKEN DURING THE SEARCH ###
############################################################

class Action:
    def __init__(self, _type, game1=None, game2=None, date=None):
        self.type = _type
        self.game1 = game1

        if self.type == 'switch':
            self.game2 = game2
        elif self.type == 'move':
            self.new_date = date
            self.original_date = game1.date
        else:
            raise ValueError(
                "Action's type should be either 'switch' or 'move'.")

    def execute_action(self, games):
        if self.type == 'switch':
            games.remove(self.game1)
            games.remove(self.game2)
            self.game1.home.remove_game(self.game1)
            self.game2.home.remove_game(self.game2)
            self.game1.away.remove_game(self.game1)
            self.game2.away.remove_game(self.game2)

            self.game1.date, self.game2.date = self.game2.date, self.game1.date

            self.game1.home.add_game(self.game1)
            self.game2.home.add_game(self.game2)
            self.game1.away.add_game(self.game1)
            self.game2.away.add_game(self.game2)
            games.add(self.game1)
            games.add(self.game2)

        elif self.type == 'move':
            games.remove(self.game1)
            self.game1.home.remove_game(self.game1)
            self.game1.away.remove_game(self.game1)

            self.game1.date = self.new_date

            self.game1.home.add_game(self.game1)
            self.game1.away.add_game(self.game1)
            games.add(self.game1)

    def undo_action(self, games):
        if self.type == 'switch':
            games.remove(self.game1)
            games.remove(self.game2)
            self.game1.home.remove_game(self.game1)
            self.game2.home.remove_game(self.game2)
            self.game1.away.remove_game(self.game1)
            self.game2.away.remove_game(self.game2)

            self.game1.date, self.game2.date = self.game2.date, self.game1.date

            self.game1.home.add_game(self.game1)
            self.game2.home.add_game(self.game2)
            self.game1.away.add_game(self.game1)
            self.game2.away.add_game(self.game2)
            games.add(self.game1)
            games.add(self.game2)

        elif self.type == 'move':
            games.remove(self.game1)
            self.game1.home.remove_game(self.game1)
            self.game1.away.remove_game(self.game1)

            self.game1.date = self.original_date

            self.game1.home.add_game(self.game1)
            self.game1.away.add_game(self.game1)
            games.add(self.game1)

    def delta_loss(self, teams, games, original_loss):
        self.execute_action(games)
        new_loss = calc_loss(teams)
        self.undo_action(games)
        return new_loss - original_loss

def random_action(games):
    action = None
    while action is None:
        if random.random() < 0.5:
            # switch games
            game1 = random.sample(games, 1)[0]
            game2 = random.sample(games, 1)[0]

            if game1.date != game2.date:
                action = Action(_type='switch', game1=game1, game2=game2)
        else:
            game1 = random.sample(games, 1)[0]
            date = random.randint(1, DAYS)
            if game1.date != date:
                action = Action(_type='move', game1=game1, date=date)

    return action

################################################################################
### SIMULATED ANNEALING WITH EXPONENTIALLY DECREASING ACCEPTANCE PROBABILITY ###
################################################################################

ALPHA = 0.99
T_START = 1
T_FINAL = 0.00001
ACC_PROB_PARAM = 0.001

def calc_loss(teams):
    return sum([team.loss() for team in teams])

def acceptance_probability(delta_loss, T):
    if delta_loss < 0:
        return 1
    return math.exp(-ACC_PROB_PARAM * delta_loss / T)

def simulated_annealing(teams, games):
    current_loss = calc_loss(teams)
    T = T_START
    while T > T_FINAL:
        print(T, current_loss)

        i = 1
        while i <= 100:
            action = random_action(games)
            action_loss = action.delta_loss(teams, games, current_loss)
            ap = acceptance_probability(action_loss, T)
            if ap > random.random():
                action.execute_action(games)
                current_loss += action_loss
            i += 1
        T *= ALPHA

#####################
### MAIN FUNCTION ###
#####################

def main():
    teams, games = set_up_schedule()

    calculate_distances(teams)

    simulated_annealing(teams, games)

    save_schedule_to_textfile(teams, games)
    print(calc_loss(teams))
    for team in teams:
        print(team.name, ":\nNumber of back-to-backs:", team.number_of_back_to_backs(),
              "\nNumber of back-to-back-to-backs:", team.number_of_back_to_backs_to_backs())
        print()

if __name__ == '__main__':
    main()


import boto3
from fastapi import UploadFile, HTTPException, status
from typing import IO, List
import io

from promy_api.infra.env import settings
from promy_api.infra.custom_type import FileType

def get_s3obj():
    return boto3.client(
        's3', aws_access_key_id=settings.aws_access_key_id, aws_secret_access_key=settings.aws_secret_access_key
    )

def get_s3url(key: str) -> str:
    return f"https://{settings.s3_bucket}.s3.{settings.s3_region}.amazonaws.com/{key}"

def upload_image(file: UploadFile, file_type: FileType, id: int) -> str:
    upload_key = f"{file_type}/{id}.{file.filename.split('.')[-1]}"
    s3 = get_s3obj()
    try:
        s3.upload_fileobj(file.file, Bucket=settings.s3_bucket, Key=upload_key, ExtraArgs={'ACL': 'public-read'})
        return get_s3url(upload_key)
    except Exception as e:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"S3에 업로드하는데 실패했습니다")

def delete_image(file_type: FileType, file_name: str) -> None:
    s3 = get_s3obj()
    try:
        s3.delete_object(Bucket=settings.s3_bucket, Key=f"{file_type}/{file_name}")
    except Exception as e:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"S3 오브젝트를 삭제하는데 실패했습니다")