# GAUSIAN NAIVE BAYES
# Nacho

# neccesary imports
import warnings

warnings.filterwarnings(action="ignore", module="scipy", message="^internal gelsd")

import pandas as pd
import seaborn as sb
import matplotlib.pyplot as plt

plt.rcParams['figure.figsize'] = (16, 9)
plt.style.use('ggplot')

# load data from dataset (csv)
# cargamos los datos de entrada

from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from sklearn.metrics import confusion_matrix
from sklearn.naive_bayes import GaussianNB  # nuestro algotirmo estadistico
from sklearn.feature_selection import SelectKBest

data = pd.read_csv("./Credit_Risk_Train_data.csv")
# eliminamos la info que creemos que es redundante, de esta forma
# al haber menos columnas para la prediccion aumentamos el accuracy pero tenemos mas sesgo

data.fillna(0, inplace=True);
reduced = data.drop(['Education', 'Self_Employed', 'Married', 'Property_Area'], axis=1)
reduced.describe()

# visualizamos nuestros datos, media, mediana, std,etc
data = reduced
print("data.shape():", data.shape)
print("data.head():", data.head(17))
print("data.describe():", data.describe())

# visualizamos cuantos items tenemos de uno u otro valor (Y/N)
print(data.groupby('Loan_Status').size())

# preparamos los datos
# a la funcion debemos pasarle valores numericos, para ello, hemos reemplazado
# el valor de algunos campos por un 0 o un 1

del data['Loan_ID']
data.Gender = data.Gender.replace({"Male": 0, "Female": 1})
data.Dependents = data.Dependents.replace({"3+": 3, "0": 0, "1": 1, "2": 2})
# estos datos ya no existen, pero los dejamos comentados para probar
# data.Married=data.Married.replace({"No": 0, "Yes": 1})
# data.Education=data.Education.replace({"Not Graduate":0 , "Graduate": 1})
# data.Self_Employed=data.Self_Employed.replace({"No":0,"Yes":1})
# data.Property_Area=data.Property_Area.replace({"Rural":0,"Urban":1,"Semiurban":2})
data.Loan_Status = data.Loan_Status.replace({"Y": 1, "N": 0})

# data['total amount']=data['LoanAmount']*1000

data.drop('Loan_Status', axis=1).hist()
# visualizamos los datos
# graficas de ingresos por numero de muestras (cliente y participe)
# grafica por genero (sobre total de items)
# grafica de historial de credito por numero de items
# grafico de cantidades a pagar por numero de items
# grafico de numero de descendientes por numero de items,
plt.show();
# por las graficas de los datos deducimos que la mayoria de los clientes que piden credito
# lo hacen con ingresos entre 600 y 10.000, sin hijos y la media del importe de 350 y genero masculino

y = data['Loan_Status']

X = data.drop(['Loan_Status'], axis=1)
y = data['Loan_Status']

# seleccion del algoritmo, escojemos todas las columnas para alimentar el modelo
best = SelectKBest(k=7)
X_new = best.fit_transform(X, y)
X_new.shape
selected = best.get_support(indices=True)
print(X.columns[selected])

used_features = X.columns[selected]

# se obtiene la matriz de correlaccion

colormap = plt.cm.viridis
plt.figure(figsize=(12, 12))
plt.title('Pearson Correlation of Features', y=1.05, size=15)
sb.heatmap(data[used_features].astype(float).corr(), linewidths=0.1, vmax=1.0, square=True, cmap=colormap,
           linecolor='white', annot=True)
plt.show()

# reservamos el 20% de nuestro set de datos para realizar las pruebas
# en dataset en training y test datasets
X_train, X_test = train_test_split(data, test_size=0.2, random_state=6)
y_train = X_train["Loan_Status"]
y_test = X_test["Loan_Status"]

# Instantiate the classifier
gnb = GaussianNB()
# Train classifier
gnb.fit(
    X_train[used_features].values,
    y_train
)
y_pred = gnb.predict(X_test[used_features])

print('Precisión en el set de Entrenamiento: {:.2f}'
      .format(gnb.score(X_train[used_features], y_train)))
print('Precisión en el set de Test: {:.2f}'
      .format(gnb.score(X_test[used_features], y_test)))

pd.set_option('display.max_columns', None)
print("data.head():", data.head(17))
# efectuamos una prediccion
b = data[0:10]
bb = b.drop(['Loan_Status'], axis=1)
print(gnb.predict(bb))
# data.drop(['Loan_Status'],1).hist()
# plt.show()

# sb.pairplot(data.dropna(), hue='Loan_Status',size=2,vars=["ApplicantIncome","CoapplicantIncome"],kind='reg')
#
# del data['Loan_ID']
# data.Gender=data.Gender.replace({"Male": 0, "Female": 1})
# data.Dependents=data.Dependents.replace({"3+": 3,"0":0,"1":1,"2":2})
# data.Married=data.Married.replace({"No": 0, "Yes": 1})
# data.Education=data.Education.replace({"Not Graduate":0 , "Graduate": 1})
# data.Self_Employed=data.Self_Employed.replace({"No":0,"Yes":1,"":0})
#

# data.Property_Area=data.Property_Area.replace({"Rural":0,"Urban":1,"Semiurban":2})
# data.Loan_Status=data.Loan_Status.replace({"Y":1,"N":0})

# data.fillna(0, inplace=True);
# pd.set_option('display.max_columns', None)
# print(data.head(5))

# X = np.array(data.drop(['Loan_Status'],1))
# y = np.array(data['Loan_Status'])
# X.shape

# aqui probamos logistic regresion
# model = linear_model.LogisticRegression()
# model.fit(X,y)

# predictions = model.predict(X)
# print(model.score(X,y))
# print(predictions)[0:1]
# P=np.array([0,0.0,0,1,0,5849,0.0,0.0,360.0,1.0,0]);
# print(X[0:2])
# print(model.predict(X[0:50]))


import lal
from functools import wraps
from astropy import units as u

##############################################################################################################
# Mapping between LAL Errors and Python
##############################################################################################################

def mapexception(func):
    """
    Wrapper to map LAL erros to Python errors
    """
    @wraps(func)
    def wrapper(*args, **kwargs):
        exception = None
        try:
            return func(*args, **kwargs)
        except RuntimeError as e:
            lalerr = lal.GetBaseErrno()
            exception = exceptionmap[lalerr] if lalerr in exceptionmap else e
            raise exception from None
    return wrapper

# Map of errors raised by LAL to Python errors
exceptionmap = {
    lal.ENOENT:     FileNotFoundError("No such file or directory"),
    lal.EIO:        OSError("I/O Error"),
    lal.ENOMEM:     MemoryError("Memory Allocation Error"),
    lal.EFAULT:     ValueError("Invalid pointer"),
    lal.EINVAL:     ValueError("Invalid Argument"),
    lal.EDOM:       ValueError("Input domain error"),
    lal.ERANGE:     ArithmeticError("Output range error"),
    lal.ENOSYS:     NotImplementedError("Function not implemented"),
    lal.EFAILED:    RuntimeError("Generic Failure"),
    lal.EBADLEN:    ValueError("Inconsistent or invalid length"),
    lal.ESIZE:      ValueError("Wrong Size"),
    lal.EDIMS:      ValueError("Wrong dimensions"),
    lal.ETYPE:      TypeError("Wrong or unknown type"),
    lal.ETIME:      ValueError("Invalid time"),
    lal.EFREQ:      ValueError("Invalid frequency"),
    lal.EUNIT:      u.UnitsError("Invalid units"),
    lal.ENAME:      ValueError("Wrong name"),
    lal.EDATA:      ValueError("Invalid data"),
    lal.ESYS:       SystemError("System Error"),
    lal.EERR:        RuntimeError("Internal Error"),
    lal.EFPINVAL:   FloatingPointError("IEEE Invalid floating point operation, eg sqrt(-1), 0/0"),
    lal.EFPDIV0:    ZeroDivisionError("IEEE Division by zero floating point error"),
    lal.EFPOVRFLW:  OverflowError("IEEE Floating point overflow error"),
    lal.EFPUNDFLW:  FloatingPointError("IEEE Floating point underflow error"),
    lal.EFPINEXCT:  FloatingPointError("IEEE Floating point inexact error"),
    lal.EMAXITER:   ArithmeticError("Exceeded maximum number of iterations"),
    lal.EDIVERGE:   ArithmeticError("Series is diverging"),
    lal.ESING:      ArithmeticError("Apparent singularity detected"),
    lal.ETOL:       ArithmeticError("Failed to reach specified tolerance"),
    lal.ELOSS:      ArithmeticError("Loss of accuracy"),
    lal.EUSR0:      RuntimeError("User defined Error"),
    lal.EUSR1:      RuntimeError("User defined Error"),
    lal.EUSR2:      RuntimeError("User defined Error"),
    lal.EUSR3:      RuntimeError("User defined Error"),
    lal.EUSR4:      RuntimeError("User defined Error"),
    lal.EUSR5:      RuntimeError("User defined Error"),
    lal.EUSR6:      RuntimeError("User defined Error"),
    lal.EUSR7:      RuntimeError("User defined Error"),
    lal.EUSR8:      RuntimeError("User defined Error"),
    lal.EUSR9:      RuntimeError("User defined Error")
    }

##############################################################################################################
# Errors callable by GWSignal.
# Each error should be subclasses from existing python errors, for eg., ValueError, Exception etc.
##############################################################################################################

class WaveformGenerationError(Exception):
    """
    Raise Error when there is an issue with waveform generation.

    Parameters
    ----------
        message   : Output message for the error.
    Returns
    -------
        Raises waveform generation error exception

    """
    def __init__(self, message="Error during waveform generation"):
        self.message = message
        super().__init__(self.message)

    def __str__(self):
        return f'{self.message}'

class DomainError(ValueError):
    """
    Raise Error when parameter passes is out of waveform domain

    Parameters
    ----------
        parameter : Parameter in question
        message   : Output message for the error.

    Returns
    -------
        Raises domain error exception
    """
    def __init__(self, parameter, message="Input parameter out of waveform domain"):
        self.parameter = parameter
        self.message = message
        super().__init__(self.message)

    def __str__(self):
        return f'{self.parameter} -> {self.message}'

class PathError(FileNotFoundError):
    """
    Raise Exception when the needed file is not at the given path

    Parameters
    ----------
        file_name : Path to the file in question.
        message   : Output message for the error.

    Returns
    -------
        Raises File not found at given path error excetion.

    """
    def __init__(self, file_path, message="File not found at "):
        self.file_path = file_path
        self.message = message
        super().__init__(self.message)

    def __str__(self):
        return f'{self.message} -> {self.file_path}'


#!/usr/bin/python

# User     : ltcastelnuovo
# Challenge: https://ringzer0ctf.com/challenges/156

import os, filecmp, requests
from shutil import rmtree
from PIL import Image
from progress.bar import Bar

GIF_PATH = "./data.gif"
FRAMES_PATH = "./frames"
COMBINED_PATH = "./combined"

def download(url):
    print("Task 1/4: Downloading gif  |################################| 1/1")
    response = requests.get(url)

    open(GIF_PATH, "wb").write(response.content)

def splitGif():
    os.mkdir(FRAMES_PATH)

    with Image.open(GIF_PATH) as tvGif:
        with Bar("Task 2/4: Splitting gif   ", max=tvGif.n_frames) as bar:
            for frame in range(tvGif.n_frames):
                tvGif.seek(frame)
                tvGif.save(FRAMES_PATH + "/" + str(frame) + ".png")

                bar.next()

def combineFrames():
    os.mkdir(COMBINED_PATH)

    framesLength = len(os.listdir(FRAMES_PATH))

    with Bar("Task 3/4: Combining frames", max=(framesLength * framesLength)) as bar:
        for i in range(framesLength):
            for j in range(framesLength):
                backPath = FRAMES_PATH + "/" + str(i) + ".png"
                frontPath = FRAMES_PATH + "/" + str(j) + ".png"

                backImage = Image.open(backPath).convert("RGBA")
                frontImage = Image.open(frontPath).convert("RGBA")

                # Overlay front image on back image
                combinedImage = Image.blend(backImage, frontImage, 0.5)
                combinedImagePath = COMBINED_PATH + "/" + str(i) + "-" + str(j) + ".png"

                # Save combined image if special
                entropy = combinedImage.entropy()

                # Random black and white images have an entropy between 2 and 3.12
                if entropy > 3.13:
                    combinedImage.save(combinedImagePath, "PNG")

                bar.next()

def filterCombinedFrames():
    combinedFrames = os.listdir(COMBINED_PATH)
    combinedFramesLength = len(combinedFrames)

    for i in range(combinedFramesLength):
        for j in range(combinedFramesLength):
            file1 = COMBINED_PATH + "/" + combinedFrames[i]
            file2 = COMBINED_PATH + "/" + combinedFrames[j]

            if file1 == file2:
                continue

            if not os.path.isfile(file1) or not os.path.isfile(file2):
                continue

            if filecmp.cmp(file1, file2):
                os.remove(file1)

    print("Task 4/4: Filtering frames |################################| 100/100")

def showResultingFrames():
    combinedFrames = os.listdir(COMBINED_PATH)

    for frame in combinedFrames:
        framePath = COMBINED_PATH + "/" + frame
        frameImage = Image.open(framePath)

        frameImage.show()

def cleanup():
    os.remove(GIF_PATH)
    rmtree(FRAMES_PATH)
    rmtree(COMBINED_PATH)

def main():
    download("https://ringzer0ctf.com/images/tv.gif")

    splitGif()
    combineFrames()
    filterCombinedFrames()
    showResultingFrames()

    cleanup()

main()


from django.contrib import admin
from .models import * # para cargar en el panel de adm.

# Register your models here.

class ArticleAdmin(admin.ModelAdmin):
    readonly_fields = ('created_at', 'updated_at')

admin.site.register(Article, ArticleAdmin)
admin.site.register(Category)

#Configurar el titulo del panel adm

admin.site.site_header="Master en Python" #Cambiar nombre del panelAdm
admin.site.site_title="Master en Python Sub"
admin.site.index_title="Panel de Gestion"


import pandas as pd
from sklearn.metrics import pairwise as similarity_measures

from pipeline import CosinePipeline
from similarity_interface import Similarity

class TracksCosineSimilarity(Similarity):
    """The class implements a Cosine similarity between a playlist vector and the tracks dataset to determine
        similar tracks to the playlist.

        This class inherits the Similarity interface.

        Attributes:
            additional_weighting (int): The weighting factor applied to weighted columns.
            playlist (DataFrame): The tracks dataset dataframe (before pipeline transformation)
            tracks (DataFrame): The playlist tracks dataframe (before pipeline transformation)
            playlist_features (DataFrame): The tracks dataset features (after transformation pipeline)
            track_features (DataFrame): The playlist tracks features (after transformation pipeline)
            similarity (Series): The ordered ranking of track similarity to the playlist vector (The index is uris)
        """
    def __init__(self, playlist: pd.DataFrame, tracks: pd.DataFrame, weighted_features: list):
        """The initialization of the Cosine Similarity class

        Args:
            playlist (DataFrame): The tracks dataset dataframe (before pipeline transformation)
            tracks (DataFrame): The playlist tracks dataframe (before pipeline transformation)
            weighted_features (list): A list of features to be weighted in order to prioritize feature importance in similarity calculation.

        """
        self.additional_weighting = 2  # Feature weighting value
        features = CosinePipeline.data_pipeline(tracks)  # Pass data through pipeline to extract features

        self.playlist = playlist
        self.tracks = tracks

        self.playlist_features, self.track_features = self.separate_playlist_from_tracks(features)
        self.weight_features(weighted_features)
        self.similarity = None

    def calculate_similarity(self):
        """Method calculates the similarity between a playlist vector and the tracks feature matrix using cosine similarity

        This calculation populates the `self.similarity` field.

        The playlist feature dataframe is mean of each feature, creating a playlist vector.
        """
        playlist_vector = self.vectorize_playlist()
        track_matrix = self.track_features.to_numpy()

        similarity_score = similarity_measures.cosine_similarity(track_matrix, playlist_vector)
        uris = self.track_features.index.tolist()

        self.similarity = pd.Series(similarity_score.T.tolist()[0], index=uris, name='sim_score')

    def access_similarity_scores(self):
        """Getter method to access the `similarity` class field.

        Returns:
            (Series): The track similarity to the playlist vector. Similarity is a Series using uris as the index.
        """
        return self.similarity

    def get_top_n(self, n: int):
        """This method should return the top-n most similar tracks as a Dataframe with essential features included.

        Note, due to the cosine similarity. A similarity value of 1 indicates a high similarity, while a value near 0 indicates a low similarity.

        Args:
            n (int): The top-n most similar tracks to the playlist vector

        Returns:
            (DataFrame): A dataframe containing the top-n tracks.
        """
        sim_df = pd.DataFrame(self.similarity, columns=['sim_score'])
        sorted_sim = sim_df.sort_values(by='sim_score', ascending=False)
        sorted_top = sorted_sim.head(n)
        return sorted_top.merge(self.tracks, left_index=True, right_on='uris')

    def separate_playlist_from_tracks(self, features: pd.DataFrame):
        """Method separates the feature dataframe (from pipeline) into tracks and playlist feature dataframes

        Args:
            features (DataFrame): The track dataset features dataframe (This contains the playlist tracks too)

        Returns:
            playlist_features (Dataframe): The playlist track features dataframe
            tracks_features (DataFrame): The track dataset features dataframe
        """
        playlist_uris = self.playlist['uris'].tolist()
        return features[features.index.isin(playlist_uris)], features[~features.index.isin(playlist_uris)]

    def vectorize_playlist(self):
        """Method vectorizes the playlist track features by determining the mean value of each track feature

        Returns:
            (Numpy vector): The playlist feature vector
        """
        playlist_vector = self.playlist_features.mean(axis=0)
        return playlist_vector.to_numpy().reshape(1, -1)

    def weight_features(self, weighted_columns: list):
        """Method weights the track dataset features (all features are normalized [0, 1]) by a scaler value
        to increase the effect of that feature in the similarity calculation.

        Wighting in cosine similarity increases the impact of the feature in the similarity calculation

        Note, this method directly alters the `self.track_features` field.

        Args:
            weighted_columns: The columns to be weighted by the additional weighting factor.

        """
        all_features = pd.Series(self.track_features.columns)
        feature_filter = all_features.isin(weighted_columns).tolist()   # Boolean filter based on if feature is in weighted columns
        binary_filter = [int(feature) for feature in feature_filter]  # Modify boolean filter into a binary filter
        filler = [1] * len(binary_filter)  # Create a filler of 1's to not effect features that have no weighting
        weights = [(self.additional_weighting * weight) + fill for weight, fill in zip(binary_filter, filler)]  # Calculate feature scaler weights

        self.track_features = self.track_features.mul(weights, axis=1)  # Weight features


from flask import Flask, render_template, request, redirect, url_for, flash, send_from_directory
from flask_session import Session
from flask_login import LoginManager, login_user, login_required, current_user, logout_user
from dao import comments_dao, follows_dao, podcast_dao, series_dao, users_dao
from models import User
from datetime import date
from werkzeug.security import generate_password_hash, check_password_hash
import os, shutil
from random import randint

app = Flask(__name__)
app.secret_key = "Xd5Y1'p(j~'ec{`Ns%Sd7Q/1ICZY$E"
app.config["SESSION_TYPE"] = "filesystem"
app.config["SESSION_PERMANENT"] = False
Session(app)

login_manager = LoginManager()
login_manager.login_view = "login"
login_manager.init_app(app)

@app.errorhandler(404)
def page_not_found(e):
    flash(e, "danger")
    return redirect(url_for("home"))

@app.route("/")
def home():
    if current_user.is_authenticated:
        series_list = series_dao.get_series(current_user.id)
        categories = series_dao.get_categories(current_user.id)
    else:
        series_list = series_dao.get_series(0)
        categories = series_dao.get_categories(0)

    return render_template("home.html", series_list=series_list, categories=categories, current_category="All", get_creator_name=users_dao.get_user_username_by_id, is_following=follows_dao.is_following)

@app.route("/category/<category>")
def home_category(category):
    if current_user.is_authenticated:
        series_list = series_dao.get_series_by_category(category, current_user.id)
        categories = series_dao.get_categories(current_user.id)
    else:
        series_list = series_dao.get_series_by_category(category, 0)
        categories = series_dao.get_categories(0)
    return render_template("home.html", series_list=series_list, categories=categories, current_category=category, get_creator_name=users_dao.get_user_username_by_id, is_following=follows_dao.is_following)

@app.route('/series/<int:series_id>')
def series(series_id):
    series_selected = series_dao.get_series_by_id(series_id)
    if not series_selected:
        flash("Series do not exist...", "danger")
        return redirect(url_for("home"))

    if current_user.is_authenticated:
        podcasts = podcast_dao.get_podcasts(series_id, current_user.id)
    else:
        podcasts = podcast_dao.get_podcasts(series_id, 0)

    return render_template("series.html", series=series_selected, podcasts=podcasts, get_creator_name=users_dao.get_user_username_by_id, is_following=follows_dao.is_following, comments=comments_dao.get_comments, str=str)

@app.route("/series/new", methods=["GET", "POST"])
@login_required
def new_series():
    if request.method == "GET":
        if not current_user.is_creator:
            flash("Only creators can create a new series...", "danger")
            return redirect(url_for("home"))

        return render_template("new-series.html")

    elif request.method == "POST":
        if not current_user.is_creator:
            flash("Only creators can create a new series...", "danger")
            return redirect(url_for("home"))

        add_series = request.form.to_dict()
        image = request.files["image"]

        # form validation
        titles = series_dao.get_series_titles()
        for title in titles:
            if title["title"].lower() == add_series["title"].lower():
                flash("This title has already been used, please choose a different one...", "warning")
                return redirect(request.referrer)

        if len(add_series["title"]) < 3:
            flash("Title is too short...", "warning")
            return redirect(url_for("new_series"))

        if add_series["title"][0] == " ":
            flash("Title cannot start with an empty space...", "warning")
            return redirect(url_for("new_series"))

        special_characters = '"!@#$%^&*()-+?_=,<>/"'
        if any(c in special_characters for c in add_series["title"]):
            flash("Title cannot contain special caracters...", "warning")
            return redirect(url_for("new_series"))

        if len(add_series["title"]) > 30:
            flash("Title is too long...", "warning")
            return redirect(url_for("new_series"))

        if len(add_series["category"]) < 3:
            flash("Category is too short...", "warning")
            return redirect(url_for("new_series"))

        if add_series["category"][0] == " ":
            flash("Category cannot start with an empty space...", "warning")
            return redirect(url_for("new_series"))

        if any(c in special_characters for c in add_series["category"]):
            flash("Title cannot contain special caracters...", "warning")
            return redirect(url_for("new_series"))

        if len(add_series["category"]) > 20:
            flash("Category is too long...", "warning")
            return redirect(url_for("new_series"))

        if len(add_series["text"]) < 100:
            flash("Description is too short...", "warning")
            return redirect(url_for("new_series"))

        if add_series["text"][0] == " ":
            flash("Description cannot start with an empty space...", "warning")
            return redirect(url_for("new_series"))

        if len(add_series["text"]) > 400:
            flash("Description is too long...", "warning")
            return redirect(url_for("new_series"))

        # handle empty date
        if add_series["date"] == "":
            add_series["date"] = date.today()

        # handle image
        if image.filename == "":
            flash("File not uploaded...", "warning")
            return redirect(url_for("new_series"))

        if "." not in image.filename or (image.filename.split(".")[1] != "jpeg" and image.filename.split(".")[1] != "jpg"):
            flash("Wrong file extension...", "warning")
            return redirect(url_for("new_series"))

        # handle creator id
        add_series["creator_id"] = current_user.id

        # save to db
        (success, id) = series_dao.add_series(add_series)

        if success:
            image.save("static/images/" + add_series["title"].lower().replace(" ", "_").replace("'", "_") + ".jpeg")
            os.mkdir("static/audio/" + str(id))
            flash("Series successfully created!", "success")
        else:
            flash("Something went wrong!", "danger")

        return redirect(url_for("home"))

@app.route("/series/<int:series_id>/edit", methods=["POST"])
@login_required
def edit_series(series_id):
    # get series data
    series_edit = series_dao.get_series_by_id(series_id)
    if not series_edit:
        flash("Series do not exist...", "danger")
        return redirect(url_for("home"))

    if not current_user.id == series_edit["creator_id"]:
        flash("Only creator can edit the series...", "danger")
        return redirect(url_for("home"))

    add_series = request.form.to_dict()
    image = request.files["image"]

    # form validation - if field not present use pre-existing data
    if add_series["title"] == "":
        add_series["title"] = series_edit["title"]
    else:
        titles = series_dao.get_series_titles()
        for title in titles:
            if title["title"] == add_series["title"]:
                flash("This title has already been used, please choose a different one...", "warning")
                return redirect(request.referrer)

        if len(add_series["title"]) < 3:
            flash("Title is too short...", "warning")
            return redirect(url_for("series", series_id=series_id))

        if add_series["title"][0] == " ":
            flash("Title cannot start with an empty space...", "warning")
            return redirect(url_for("series", series_id=series_id))

        special_characters = '"!@#$%^&*()-+?_=,<>/"'
        if any(c in special_characters for c in add_series["title"]):
            flash("Title cannot contain special caracters...", "warning")
            return redirect(url_for("series", series_id=series_id))

        if len(add_series["title"]) > 30:
            flash("Title is too long...", "warning")
            return redirect(url_for("series", series_id=series_id))

    if add_series["category"] == "":
        add_series["category"] = series_edit["category"]
    else:
        if len(add_series["category"]) < 3:
            flash("Category is too short...", "warning")
            return redirect(url_for("series", series_id=series_id))

        if add_series["category"][0] == " ":
            flash("Category cannot start with an empty space...", "warning")
            return redirect(url_for("series", series_id=series_id))

        if any(c in special_characters for c in add_series["category"]):
            flash("Title cannot contain special caracters...", "warning")
            return redirect(url_for("series", series_id=series_id))

        if len(add_series["category"]) > 20:
            flash("Category is too long...", "warning")
            return redirect(url_for("series", series_id=series_id))

    if add_series["text"] == "":
        add_series["text"] = series_edit["text"]
    else:
        if len(add_series["text"]) < 100:
            flash("Description is too short...", "warning")
            return redirect(url_for("series", series_id=series_id))

        if add_series["text"][0] == " ":
            flash("Description cannot start with an empty space...", "warning")
            return redirect(url_for("series", series_id=series_id))

        if len(add_series["text"]) > 400:
            flash("Description is too long...", "warning")
            return redirect(url_for("series", series_id=series_id))

    if add_series["date"] == "":
        add_series["date"] = series_edit["date"]

    # handle image
    if image.filename != "":
        if "." not in image.filename or (image.filename.split(".")[1] != "jpeg" and image.filename.split(".")[1] != "jpg"):
            flash("Wrong file extension...", "warning")
            return redirect(url_for("series", series_id=series_id))

    add_series["creator_id"] = current_user.id
    add_series["id"] = series_edit["id"]

    success = series_dao.update_series(add_series)

    if success:
        if image.filename == "":
            os.rename("static/images/" + series_edit["title"].lower().replace(" ", "_").replace("'", "_") + ".jpeg", "static/images/" + add_series["title"].lower().replace(" ", "_").replace("'", "_") + ".jpeg")
        else:
            image.save("static/images/" + add_series["title"].lower().replace(" ", "_").replace("'", "_") + ".jpeg")
            if add_series["title"].lower() != series_edit["title"].lower():
                os.remove("static/images/" + series_edit["title"].lower().replace(" ", "_").replace("'", "_") + ".jpeg")
        flash("Series successfully updated!", "success")
    else:
        flash("Something went wrong!", "danger")

    return redirect(url_for("series", series_id=series_id))

@app.route("/series/<int:series_id>/delete")
@login_required
def delete_series(series_id):
    # check that series exist
    del_series = series_dao.get_series_by_id(series_id)

    if not del_series:
        flash("This series does not exist...", "danger")
        return redirect(url_for("home"))

    if not current_user.id == del_series["creator_id"]:
        flash("Only creator can delete the series...", "danger")
        return redirect(url_for("home"))

    # delete from db
    success = series_dao.del_series(series_id)
    if not success:
        flash("Something went wrong...", "danger")
        return redirect(request.referrer)

    # remove files from filesystem
    os.remove("static/images/" + del_series["title"].lower().replace(" ", "_").replace("'", "_") + ".jpeg")
    shutil.rmtree("static/audio/" + str(del_series["id"]))

    flash("Series deleted correctly!", "success")
    return redirect(url_for("home"))

@app.route("/series/<int:series_id>/add_podcast", methods=["POST"])
@login_required
def new_podcast(series_id):
    # check that series exist
    curr_series = series_dao.get_series_by_id(series_id)
    if not curr_series:
        flash("Cannot add podcast to non existent series...", "danger")
        return redirect(url_for("home"))

    # check user is creator of the series
    if not current_user.id == curr_series["creator_id"]:
        flash("Only creator of the series can add new podcasts...", "danger")
        return redirect(url_for("home"))

    add_podcast = request.form.to_dict()
    audio = request.files["audio"]

    # form validation
    titles = podcast_dao.get_podcasts_titles(series_id)
    for title in titles:
        if title["title"].lower() == add_podcast["title"].lower():
            flash("This title has already been used, please choose a different one...", "warning")
            return redirect(request.referrer)

    if len(add_podcast["title"]) < 3:
        flash("Title is too short...", "warning")
        return redirect(url_for("series", series_id=series_id))

    if add_podcast["title"][0] == " ":
        flash("Title cannot start with an empty space...", "warning")
        return redirect(url_for("series", series_id=series_id))

    special_characters = '"!@#$%^&*()-+?_=,<>/"'
    if any(c in special_characters for c in add_podcast["title"]):
        flash("Title cannot contain special caracters...", "warning")
        return redirect(url_for("series", series_id=series_id))

    if len(add_podcast["title"]) > 30:
        flash("Title is too long...", "warning")
        return redirect(url_for("series", series_id=series_id))

    if len(add_podcast["description"]) < 30:
        flash("Description is too short...", "warning")
        return redirect(url_for("series", series_id=series_id))

    if add_podcast["description"][0] == " ":
        flash("Description cannot start with an empty space...", "warning")
        return redirect(url_for("series", series_id=series_id))

    if len(add_podcast["description"]) > 150:
        flash("Description is too long...", "warning")
        return redirect(url_for("series", series_id=series_id))

    if add_podcast["date"] == "":
        add_podcast["date"] = date.today()

    # handle audio file
    if audio.filename == "":
        flash("Audio is not present...", "warning")
        return redirect(url_for("series", series_id=series_id))

    if "." not in audio.filename or audio.filename.split(".")[1] != "mp3":
        flash("Wrong file extension...", "warning")
        return redirect(url_for("new_podcast"))

    add_podcast["series_id"] = curr_series["id"]
    add_podcast["rnd_seed"] = randint(1,10)

    # save to db
    success = podcast_dao.add_podcast(add_podcast)

    if success:
        audio.save("static/audio/" + str(curr_series["id"]) + "/" + add_podcast["title"].lower().replace(" ", "_").replace("'", "_") + str(add_podcast["rnd_seed"]) + ".mp3")
        flash("Podcast successfully added!", "success")
    else:
        flash("Something went wrong!", "danger")

    return redirect(url_for("series", series_id=curr_series["id"]))

@app.route("/podcast/<int:podcast_id>/edit", methods=["POST"])
@login_required
def edit_podcast(podcast_id):
    # check that podcast exsists
    podcast_edit = podcast_dao.get_podcast(podcast_id)
    if not podcast_edit:
        flash("Podcast do not exist...", "danger")
        return redirect(url_for("home"))

    # get series data
    series_pod = series_dao.get_series_by_id(podcast_edit["series_id"])
    series_id = series_pod["id"]

    # check user is creator of the podcast
    if not current_user.id == series_pod["creator_id"]:
        flash("Only creator of the series can edit podcasts...", "danger")
        return redirect(url_for("series", series_id=series_id))

    add_podcast = request.form.to_dict()
    audio = request.files["audio"]

    # validate form
    if add_podcast["title"] == "":
        add_podcast["title"] = podcast_edit["title"]
    else:
        titles = podcast_dao.get_podcasts_titles(series_id)
        for title in titles:
            if title["title"] == add_podcast["title"]:
                flash("This title has already been used, please choose a different one...", "warning")
                return redirect(request.referrer)

        if len(add_podcast["title"]) < 3:
            flash("Title is too short...", "warning")
            return redirect(url_for("series", series_id=series_id))

        if add_podcast["title"][0] == " ":
            flash("Title cannot start with an empty space...", "warning")
            return redirect(url_for("series", series_id=series_id))

        special_characters = '"!@#$%^&*()-+?_=,<>/"'
        if any(c in special_characters for c in add_podcast["title"]):
            flash("Title cannot contain special caracters...", "warning")
            return redirect(url_for("series", series_id=series_id))

        if len(add_podcast["title"]) > 30:
            flash("Title is too long...", "warning")
            return redirect(url_for("series", series_id=series_id))

    if add_podcast["description"] == "":
        add_podcast["description"] = podcast_edit["description"]
    else:
        if len(add_podcast["description"]) < 30:
            flash("Description is too short...", "warning")
            return redirect(url_for("series", series_id=series_id))

        if add_podcast["description"][0] == " ":
            flash("Description cannot start with an empty space...", "warning")
            return redirect(url_for("series", series_id=series_id))

        if len(add_podcast["description"]) > 150:
            flash("Description is too long...", "warning")
            return redirect(url_for("series", series_id=series_id))

    # handle date
    if add_podcast["date"] == "":
        add_podcast["date"] = podcast_edit["date"]

    # handle audio
    if audio.filename != "":
        if "." not in audio.filename or audio.filename.split(".")[1] != "mp3":
            flash("Wrong file extension...", "warning")
            return redirect(request.referrer)
        if podcast_edit["title"].lower() == add_podcast["title"].lower():
            old_seed = podcast_edit["rnd_seed"]
            new_seed = old_seed
            while new_seed == old_seed:
                new_seed = randint(1,10)
            add_podcast["rnd_seed"] = new_seed
    else:
        add_podcast["rnd_seed"] = podcast_edit["rnd_seed"]

    add_podcast["series_id"] = podcast_edit["series_id"]
    add_podcast["id"] = podcast_edit["id"]

    success = podcast_dao.update_podcast(add_podcast)

    if success:
        if audio.filename == "":
            os.rename("static/audio/" + str(series_pod["id"]) + "/" + podcast_edit["title"].lower().replace(" ", "_").replace("'", "_") + str(podcast_edit["rnd_seed"]) + ".mp3", "static/audio/" + str(series_pod["id"]) + "/" + add_podcast["title"].lower().replace(" ", "_").replace("'", "_") + str(add_podcast["rnd_seed"]) + ".mp3")
        else:
            audio.save("static/audio/" + str(series_pod["id"]) + "/" + add_podcast["title"].lower().replace(" ", "_").replace("'", "_") + str(add_podcast["rnd_seed"]) + ".mp3")
            os.remove("static/audio/" + str(series_pod["id"]) + "/" + podcast_edit["title"].lower().replace(" ", "_").replace("'", "_") + str(podcast_edit["rnd_seed"]) + ".mp3")
        flash("Podcast successfully updated!", "success")
    else:
        flash("Something went wrong!", "danger")

    return redirect(url_for("series", series_id=series_pod["id"]))

@app.route("/podcast/<int:podcast_id>/delete_podcast")
@login_required
def delete_podcast(podcast_id):
    # check that podcast exist
    del_podcast = podcast_dao.get_podcast(podcast_id)
    if not del_podcast:
        flash("This podcast does not exist...", "danger")
        return redirect(url_for("home"))

    # check if user is creator of the series
    podcast_series = series_dao.get_series_by_id(del_podcast["series_id"])

    if not current_user.id == podcast_series["creator_id"]:
        flash("Only creator of the series can delete podcasts...", "danger")
        return redirect(url_for("series", series_id=podcast_series["id"]))

    success = podcast_dao.del_podcast(podcast_id)
    if not success:
        flash("Something went wrong...", "danger")
        return redirect(request.referrer)

    os.remove("static/audio/" + str(podcast_series["id"]) + "/" + del_podcast["title"].lower().replace(" ", "_").replace("'", "_") + str(del_podcast["rnd_seed"]) + ".mp3")

    flash("Podcast deleted correctly!", "success")
    return redirect(url_for("series", series_id=podcast_series["id"]))

@app.route("/profile")
@login_required
def profile():
    follows = follows_dao.get_follows(current_user.id)

    return render_template("profile.html", follows=follows, get_series_by_id=series_dao.get_series_by_id, get_creator=users_dao.get_user_username_by_id)

@app.route("/follow/<int:series_id>")
@login_required
def follow(series_id):
    # check if series exists
    series_follow = series_dao.get_series_by_id(series_id)
    if not series_follow:
        flash("The series you are trying to follow does not exist...", "danger")
        return redirect(url_for("home"))

    # check if user already follows
    already_follows = follows_dao.is_following(current_user.id, series_id)

    if already_follows:
        flash("You already follow this series...", "warning")
        return redirect(request.referrer)

    new_follow = {"user_id": current_user.id, "series_id": series_id}
    success = follows_dao.add_follow(new_follow)
    if not success:
        flash("Something went wrong...", "danger")

    return redirect(request.referrer)

@app.route("/unfollow/<int:series_id>")
@login_required
def unfollow(series_id):
    # check if user is following
    del_follow = follows_dao.is_following(current_user.id, series_id)

    if del_follow:
        success = follows_dao.delete_follow(del_follow["id"])
        if not success:
            flash("Something went wrong...", "danger")
    else:
        flash("You are not following this series...", "danger")
    return redirect(request.referrer)

@app.route("/podcast/<int:podcast_id>/new_comment", methods=["POST"])
@login_required
def new_comment(podcast_id):
    comment = request.form.to_dict()

    # form validation
    if len(comment["comment"]) < 3:
        flash("Comment is too short...", "warning")
        return redirect(request.referrer)

    if comment["comment"][0] == " ":
        flash("Comment cannot start with an empty space...", "warning")
        return redirect(url_for(request.referrer))

    if len(comment["comment"]) > 300:
        flash("Comment is too long...", "warning")
        return redirect(request.referrer)

    # handle date
    comment["date"] = date.today()

    comment["user_id"] = current_user.id
    comment["podcast_id"] = podcast_id

    success = comments_dao.post_comment(comment)

    if not success:
        flash("Something went wrong!", "danger")

    return redirect(request.referrer)

@app.route("/podcast/comments/<int:comment_id>/edit", methods=["POST"])
@login_required
def edit_comment(comment_id):
    comment = request.form.to_dict()
    edit_comment = comments_dao.get_comment(comment_id)

    # check user is comment creator
    if current_user.id != edit_comment["user_id"]:
        flash("Only creator of the comment can edit it...", "danger")
        return redirect(request.referrer)

    # form validation
    if comment["comment"] == "":
        comment["comment"] = edit_comment["comment"]
    else:
        if len(comment["comment"]) < 3:
            flash("Comment is too short...", "warning")
            return redirect(request.referrer)

        if comment["comment"][0] == " ":
            flash("Comment cannot start with an empty space...", "warning")
            return redirect(url_for(request.referrer))

        if len(comment["comment"]) > 300:
            flash("Comment is too long...", "warning")
            return redirect(request.referrer)

    # handle date
    comment["date"] = date.today()

    comment["id"] = comment_id
    comment["podcast_id"] = edit_comment["podcast_id"]

    success = comments_dao.update_comment(comment)

    if not success:
        flash("Something went wrong!", "danger")

    return redirect(request.referrer)

@app.route("/podcast/<int:comment_id>/del_comment")
@login_required
def delete_comment(comment_id):
    # check that comment exist
    comment = comments_dao.get_comment(comment_id)

    if not comment:
        flash("This comment does not exist...", "danger")
        return redirect(url_for("home"))

    # check if user posted the comment
    if not current_user.id == comment["user_id"]:
        flash("Only user who posted the comment can delete it...", "danger")
        return redirect(url_for("home"))

    success = comments_dao.del_comment(comment_id)
    if not success:
        flash("Something went wrong...", "danger")
        return redirect(request.referrer)

    return redirect(request.referrer)

@login_manager.user_loader
def load_user(id):
    user = users_dao.get_user_by_id(id)

    if user is not None:
        user_obj = User(user["id"], user["username"], user["email"], user["password"], user["is_creator"])
    else:
        user_obj = None

    return user_obj

@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "GET":
        return render_template("login.html")
    elif request.method == "POST":
        user = request.form.to_dict()

        user_db = users_dao.get_user_by_email(user["email"])

        if not user_db or not check_password_hash(user_db["password"], user["password"]):
            flash("Email or password are wrong, try again...", "warning")
            return redirect(request.referrer)
        else:
            user_obj = User(user_db["id"], user_db["username"], user_db["email"], user_db["password"], user_db["is_creator"])
            success = login_user(user_obj, True)

            if success:
                return redirect(url_for("home"))
            else:
                flash("Something went wrong...", "danger")
                return redirect(request.referrer)

@app.route("/signup", methods=["GET", "POST"])
def signup():
    if request.method == "GET":
        return render_template("signup.html")
    elif request.method == "POST":
        new_user = request.form.to_dict()

        # check that new user is not already signed up (check by email)
        user_db = users_dao.get_user_by_email(new_user["email"])

        if user_db:
            flash("This email has already been used...", "warning")
            return redirect(request.referrer)

        else:
            # check all fields are correct
            if new_user["username"] == "":
                flash("Username is required...", "warning")
                return redirect(request.referrer)

            if len(new_user["username"]) > 20:
                flash("Username is too long...", "warning")
                return redirect(request.referrer)

            if new_user["email"] == "":
                flash("Email is required...", "warning")
                return redirect(request.referrer)

            if len(new_user["password"]) < 8:
                flash("Password is too short...", "warning")
                return redirect(request.referrer)

            if len(new_user["password"]) > 30:
                flash("Password is too long...", "warning")
                return redirect(request.referrer)

            new_user["password"] = generate_password_hash(new_user["password"], method="sha256")
            if new_user.get("is_creator"):
                new_user["is_creator"] = 1
            else:
                new_user["is_creator"] = 0

            success = users_dao.add_user(new_user)

            if success:
                flash("User successfully created!", "success")
                return redirect(url_for("login"))
            else:
                flash("Something went wrong...", "danger")
                return redirect(request.referrer)

@app.route("/logout")
@login_required
def logout():
    logout_user()
    return redirect(url_for("home"))

if __name__ == "__main__":
    app.run("localhost", 3000, True)


#!/usr/bin/env python3
import math
import os

from common.connect_to_rabbitmq import connect_to_rabbitmq
from common.send_end import send_end

with connect_to_rabbitmq() as channel:
    node_id = os.getenv('NODE_ID')
    question_user_with_total_score_queue_name = f'question_user_with_total_score_{node_id}'
    channel.queue_declare(queue=question_user_with_total_score_queue_name)

    answer_user_with_total_score_queue_name = f'answer_user_with_total_score_{node_id}'
    channel.queue_declare(queue=answer_user_with_total_score_queue_name)

    channel.queue_declare(queue='user_with_joined_score')

    scores_by_user_id = {}
    has_all_question_users = False
    has_all_answer_users = False

    def enqueue_remaining_users():
        for user_id, scores in scores_by_user_id.items():
            channel.basic_publish(
                exchange='',
                routing_key='user_with_joined_score',
                body=' '.join((user_id, str(scores.get('answer', 0) + scores.get('question', 0))))
            )
        send_end(channel, exchange='', routing_key='user_with_joined_score')
        exit(0)

    def handle_user(method, body, is_answer):
        global has_all_answer_users
        global has_all_question_users
        global answer_user_with_total_score_queue_name
        global question_user_with_total_score_queue_name
        body = body.decode("ISO-8859-1")
        if body == '__end__':
            if is_answer:
                has_all_answer_users = True
            else:
                has_all_question_users = True
            if has_all_answer_users and has_all_question_users:
                enqueue_remaining_users()
        else:
            user_id, score = body.split(' ')
            if user_id not in scores_by_user_id:
                scores_by_user_id[user_id] = {}
            score = float(score)
            score = 0 if math.isnan(score) else score
            key = 'answer' if is_answer else 'question'
            scores_by_user_id[user_id][key] = score
            if 'answer' in scores_by_user_id[user_id] and 'question' in scores_by_user_id[user_id]:
                scores = scores_by_user_id.pop(user_id)
                channel.basic_publish(exchange='', routing_key='user_with_joined_score', body=' '.join((user_id, str(scores['answer'] + scores['question']))))
        channel.basic_ack(delivery_tag=method.delivery_tag)

    def handle_question_user(_ch, method, _properties, body):
        handle_user(method, body, is_answer=False)

    channel.basic_consume(queue=question_user_with_total_score_queue_name, on_message_callback=handle_question_user)

    def handle_answer_user(_ch, method, _properties, body):
        handle_user(method, body, is_answer=True)

    channel.basic_consume(queue=answer_user_with_total_score_queue_name, on_message_callback=handle_answer_user)

    channel.start_consuming()


import requests
import constants
from urllib.request import urlopen, URLError

def login_attempt(key):
    print('login_attempt')
    status = check_server()
    print(status)
    if status == requests.codes.not_found:
        pack = constants.ServerAnswer(requests.codes.not_found)
        return pack
    ans = requests.get(constants.LOGIN_URL + key)
    if ans.status_code == requests.codes.ok:
        pack = constants.ServerAnswer(requests.codes.ok, ans.text)
        return pack
    else:
        pack = constants.ServerAnswer(requests.codes.unauthorized)
        return pack

def check_server():
    a = ping(constants.HTTP_START + constants.SERVER_IP)
    if (a):
        print("Server is reachable")
        return requests.codes.ok
    else:
        return requests.codes.not_found

def ping(host):
    try:
        urlopen(host, timeout=3)
        return True
    except URLError as a:
        print(a.strerror)
        return False


import matplotlib.pyplot as plt
import numpy as np
from ase.atoms import Atoms
from nff.io.ase import AtomsBatch
from scipy.cluster.hierarchy import fcluster, linkage
from scipy.spatial import distance
from scipy.special import softmax

def get_atoms_batch(slab: Atoms, neighbor_cutoff: float, nff_calc, device: str):
    return AtomsBatch(
        positions=slab.positions,
        numbers=slab.numbers,
        cell=slab.cell,
        pbc=True,
        cutoff=neighbor_cutoff,
        props={"energy": 0, "energy_grad": []},
        calculator=nff_calc,
        requires_large_offsets=True,
        directed=True,
        device=device,
    )

def filter_distances(slab, ads=["O"], cutoff_distance: float = 1.5):
    """This function filters out slabs that have atoms too close to each other based on a
    specified cutoff distance.

    Parameters
    ----------
    slab : ase.atoms.Atoms or catkit.gratoms.Gratoms or AtomsBatch
        The `slab` is the surface structure
    ads
        The ads parameter is a list of chemical symbols representing the atoms that are being checked
        for their distances from each other. By default, it is set to ["O"], which means that the function
        will check the distances of oxygen atoms from each other.
    cutoff_distance
        The minimum distance allowed between any two atoms in the slab. If any two adsorbate
        atoms are closer than this distance, the function will return False.

    Returns
    -------
        a boolean value. It returns True if all distances between the specified adsorbates in the given
    slab are greater than the specified cutoff distance, and False otherwise.

    """
    # Checks distances of all adsorbates are greater than cutoff
    ads_arr = np.isin(slab.get_chemical_symbols(), ads)
    unique_dists = np.unique(
        np.triu(slab.get_all_distances(mic=True)[ads_arr][:, ads_arr])
    )
    # get upper triangular matrix of ads dist
    if any(unique_dists[(unique_dists > 0) & (unique_dists <= cutoff_distance)]):
        return False  # fail because atoms are too close
    return True

def get_cluster_centers(points: np.ndarray, n_clusters: int):
    """
    This function performs hierarchical clustering on a set of points and returns the centers of the resulting clusters.

    Parameters
    ----------
    points : numpy.ndarray
        A numpy array of shape (n_points, n_dimensions) containing the points to cluster.
    n_clusters : int
        The number of clusters to create.

    Returns
    -------
    centers : numpy.ndarray
        A numpy array of shape (n_clusters, n_dimensions) containing the centers of the resulting clusters.
    labels : numpy.ndarray
        A numpy array of shape (n_points,) containing the cluster labels for each point.
    """

    # Do hierarchical clustering
    Z = linkage(points, "ward")

    # Cut the tree to create k clusters
    labels = fcluster(Z, n_clusters, criterion="maxclust")

    centers = []
    for i in range(1, n_clusters + 1):
        # Get all points in cluster i
        cluster_points = points[labels == i]

        # Compute and store the center of the cluster
        center = np.mean(cluster_points, axis=0)
        centers.append(center)

    return np.array(centers), labels

# # Test
# points = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12], [13, 14, 15]])
# n_clusters = 3
# centers, labels = get_cluster_centers(points, n_clusters)

# for i in range(n_clusters):
#     print(f"Center of cluster {i + 1}: {centers[i]}")
#     print(f"Points in cluster {i + 1}: {points[labels == i + 1]}")

def find_closest_points_indices(points, centers, labels):
    closest_points_indices = []
    for i in range(1, len(centers) + 1):
        # Get indices of all points in cluster i
        cluster_indices = np.where(labels == i)[0]

        # Get all points in cluster i
        cluster_points = points[cluster_indices]

        # Calculate the distances from all points to the center point
        distances = np.linalg.norm(cluster_points - centers[i - 1], axis=1)

        # Find the index of the point with the smallest distance to the center point
        closest_point_index = cluster_indices[np.argmin(distances)]
        closest_points_indices.append(closest_point_index)

    return np.array(closest_points_indices)

def plot_clustering_results(
    points,
    n_clusters,
    labels,
    closest_points_indices,
    save_folder=".",
):
    # Define colors
    colors = ["b", "g", "r", "c", "m", "y", "k"]

    # Create a larger plot
    plt.figure(figsize=(10, 7))

    # Create a scatter plot of all points, color-coded by cluster
    for i in range(1, n_clusters + 1):
        cluster_points = points[labels == i]
        plt.scatter(
            cluster_points[:, 0],
            cluster_points[:, 1],
            color=colors[i % len(colors)],
            alpha=0.6,
            edgecolor="black",
            linewidth=1,
            s=100,
            label=f"Cluster {i}",
        )

    # Mark the closest points to the centroid in each cluster
    for i in range(n_clusters):
        closest_point = points[closest_points_indices[i]]
        plt.scatter(
            closest_point[0],
            closest_point[1],
            marker="*",
            color="black",
            edgecolor="black",
            linewidth=1,
            s=200,
        )

    plt.grid(True)
    plt.xlabel("Dimension 1", fontsize=14)
    plt.ylabel("Dimension 2", fontsize=14)
    plt.title("2D representation of points and clusters", fontsize=16)
    plt.legend(fontsize=12)
    plt.savefig(f"{save_folder}/clustering_results.png")

# # Test
# points = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12], [13, 14, 15]])
# n_clusters = 3
# centers, labels = get_cluster_centers(points, n_clusters)
# closest_points_indices = find_closest_points_indices(points, centers, labels)

# for i in range(n_clusters):
#     print(f"Center of cluster {i + 1}: {centers[i]}")
#     print(f"Index of closest point to center in cluster {i + 1}: {closest_points_indices[i]}")
#     print(f"Points in cluster {i + 1}: {points[labels == i + 1]}\n")

def compute_distance_weight_matrix(ads_coords, distance_decay_factor):
    # Compute pairwise distance matrix
    ads_coord_distances = distance.cdist(ads_coords, ads_coords, "euclidean")

    # Compute distance decay matrix using softmax
    distance_weight_matrix = softmax(
        -ads_coord_distances / distance_decay_factor, axis=1
    )

    assert np.allclose(np.sum(distance_weight_matrix, axis=1), 1.0)

    return distance_weight_matrix

def plot_distance_weight_matrix(distance_weight_matrix, save_folder="."):
    # Define colors
    colors = ["b", "g", "r", "c", "m", "y", "k"]

    # Create a larger plot
    plt.figure(figsize=(10, 7))

    # Display the distance weight matrix as an image
    plt.imshow(distance_weight_matrix, cmap="hot", interpolation="nearest")

    # Add a colorbar to the figure to show how colors correspond to values
    plt.colorbar()

    plt.xlabel("Dimension 1", fontsize=14)
    plt.ylabel("Dimension 2", fontsize=14)
    plt.title("Distance Weight Matrix")
    plt.savefig(f"{save_folder}/distance_weight_matrix.png")

def plot_decay_curve(decay_factor, save_folder="."):
    plt.figure(figsize=(10, 7))
    distances = np.linspace(0, 3 * decay_factor, 100)
    factor = softmax(-distances / decay_factor)
    plt.plot(distances, factor, color="blue", label="Decay Factor")

    plt.xlabel("Distance [Å]", fontsize=14)
    plt.ylabel("Probability density", fontsize=14)
    plt.title("Distance Decay Plot")
    plt.legend(fontsize=12)
    plt.savefig(f"{save_folder}/distance_weight_decay.png")

def plot_specific_weights(coords, weights, site_idx, save_folder=".", run_iter=0):
    # Create a larger plot
    plt.figure(figsize=(10, 7))
    curr_site = coords[site_idx]

    # Create a scatter plot of all points, color-coded by weights
    plt.scatter(
        coords[:, 0],
        coords[:, 1],
        c=weights,
        alpha=0.6,
        edgecolor="black",
        linewidth=1,
        s=100,
    )
    plt.scatter(
        curr_site[0],
        curr_site[1],
        marker="*",
        color="black",
        edgecolor="black",
        linewidth=1,
        s=200,
    )
    # Add a colorbar to the figure to show how colors correspond to values
    plt.colorbar()
    plt.grid(True)
    plt.xlabel("Dimension 1", fontsize=14)
    plt.ylabel("Dimension 2", fontsize=14)
    plt.title(
        "2D representation of adsorption sites color-coded by weights", fontsize=16
    )
    plt.savefig(f"{save_folder}/specific_weights_on_lattice_iter_{run_iter:06}.png")
    plt.close()


from __future__ import nested_scopes
from numpy.core.fromnumeric import sort
import pandas as pd
import json
import numpy as np
from pandas._libs.algos import unique_deltas
from pandas.core import series

def get_data_from_csv():
    df = pd.read_csv('../Data/korea_geo_name.csv', encoding="utf-8")
    df = df[df["시도"].notna()]
    return df

def handle_dup_siGunGu(geo_lv2):
    sigungu = geo_lv2[geo_lv2["소분류"].isna()]  # 시군구명 + 시도명
    sido = geo_lv2[geo_lv2.T.count() == 2]  # 시도
    sido_for_dict = sido.copy()  # 영어 시도명 딕셔너리
    # 인덱스 스왑
    sido_for_dict["orgidx"] = sido_for_dict.index
    sido_for_dict.index = sido_for_dict["시도"]
    sido_in_eng_dict = sido_for_dict.to_dict('index')
    sigungu_name_count = sigungu["시군구"].value_counts()  # 모든 시군구이름 중복갯수
    dup_sigungu_name = sigungu_name_count.\
        loc[sigungu_name_count != 1].index  # 중복 시군구 ['중구','동구',...]
    dup_sigungu_json = sigungu_name_count.to_json()
    with open("../Data/lv2_dup_name.json", "w") as f:
        f.write(
            json.dumps(
                json.loads(dup_sigungu_json),
                indent=2,
                sort_keys=False,
                ensure_ascii=False)
        )

    def dup_engname_to_nest(
            x, dup_sigungu_name, sido, sigungu, sido_in_eng_dict
    ):
        """
        Jung-gu -> Jung-gu in Seoul

        """
        if (x.name == "시군구"):
            dup_idx = x[x.isin(dup_sigungu_name)].index
            sido_eng_for_dup_sigungu = \
                sigungu.loc[dup_idx, "시도"].apply(
                    lambda x: sido_in_eng_dict[x]["영문 표기"]
                )
            sigungu.loc[dup_idx, "영문 표기"] = sigungu.loc[dup_idx, "영문 표기"]\
                + " in "\
                + sido_eng_for_dup_sigungu
            sigungu["지명 갯수"] = 1
            sigungu.loc[dup_idx, "지명 갯수"] = 2
            return sigungu

    sigungu[["시도", "시군구"]].apply(
        lambda x: dup_engname_to_nest(
            x,
            dup_sigungu_name,
            sido,
            sigungu,
            sido_in_eng_dict
        )
    )
    geo_lv2["지명 갯수"] = 1
    geo_lv2[geo_lv2["소분류"].isna()] = sigungu
    return sigungu

def write_geo_lv2(kor_geo_data):
    nested_sigungu_df = handle_dup_siGunGu(kor_geo_data)
    geo_lv2 = nested_sigungu_df.copy()
    geo_lv2 = geo_lv2[geo_lv2.T.count() == 5]
    geo_lv2.drop(columns=["읍면동", "소분류", "중분류"], inplace=True)
    j_lv2 = geo_lv2.groupby(["시도"])\
        .apply(lambda x: x[["시군구", "영문 표기", "지명 갯수"]].to_dict('records'))\
        .reset_index()\
        .rename(columns={0: "시군구_영어"})\
        .to_json(orient="records")
    with open("../Data/lv2_korea_geo.json", "w") as outfile:
        outfile.write(
            json.dumps(json.loads(j_lv2),
                       indent=2,
                       sort_keys=False,
                       ensure_ascii=False)
        )
    return nested_sigungu_df

def write_geo_lv1(kor_geo_data):
    geo_lv1 = kor_geo_data.copy()
    geo_lv1 = geo_lv1[geo_lv1.T.count() == 2]
    geo_lv1.drop(columns=["읍면동", "시군구"], inplace=True)
    geo_lv1_json = geo_lv1.to_json(orient="records")

    with open("../Data/lv1_korea_geo.json", "w") as outfile:
        outfile.write(
            json.dumps(
                json.loads(geo_lv1_json),
                indent=2,
                sort_keys=False,
                ensure_ascii=False
            )
            )

def gen_nest_emd_english(kor_geo_data, u_myeon_dong, u_m_d_dup_df, write=False):
    si_gun_gu = kor_geo_data[(kor_geo_data["소분류"].isna())
                             &
                             (kor_geo_data.T.count() == 5)
                             ]
    u_m_d_dup_df_left = u_m_d_dup_df.copy()
    u_m_d_dup_df_left = u_m_d_dup_df_left.merge(
        si_gun_gu,
        how="left",
        on="중분류"
    )
    u_m_d_dup_df_left.index = u_m_d_dup_df.index
    u_m_d_dup_df.loc[u_m_d_dup_df.index, "영문 표기"] = \
        u_m_d_dup_df_left.loc[u_m_d_dup_df.index, "영문 표기_x"]\
        + " in "\
        + u_m_d_dup_df_left.loc[u_m_d_dup_df.index, "영문 표기_y"]
    u_m_d_dup_df.loc[u_m_d_dup_df.index, "지명 갯수"] = \
        u_m_d_dup_df.loc[u_m_d_dup_df.index, "영문 표기"].copy().apply(
        lambda x: len(x.split("in"))
    )
    if write:
        u_m_d_dup_df.copy().\
            sort_values(by=["읍면동"]).to_csv("../Data/lv3_dup_handled_name.csv")
    kor_geo_data.loc[u_m_d_dup_df.index] = u_m_d_dup_df.loc[u_m_d_dup_df.index]
    return kor_geo_data

def write_geo_lv3(kor_geo_data, write=False):
    u_myeon_dong = kor_geo_data[kor_geo_data["소분류"].notna()]
    u_m_d_column = u_myeon_dong["읍면동"]
    u_m_d_count = u_m_d_column.value_counts()
    u_m_d_dup_name = u_m_d_count[u_m_d_count != 1].index
    u_m_d_dup_df = u_myeon_dong[u_myeon_dong["읍면동"].isin(u_m_d_dup_name)]
    if write:
        u_m_d_dup_df.copy().\
            sort_values(by=["읍면동"]).to_csv("../Data/lv3_dup_name.csv")
    gen_nest_emd_english(kor_geo_data, u_myeon_dong, u_m_d_dup_df)

    u_myeon_dong_json = kor_geo_data.copy()[kor_geo_data["소분류"].notna()]
    u_myeon_dong_json.drop(columns=["소분류", "중분류"], inplace=True)
    u_myeon_dong_json_test = u_myeon_dong_json.copy().groupby(["시군구"])

    print(u_myeon_dong_json_test)
    j_lv3 = u_myeon_dong_json.groupby(["시도", "시군구"])\
        .apply(lambda x: x[["읍면동", "영문 표기", "지명 갯수"]].to_dict('records'))\
        .reset_index()\
        .rename(columns={0: "읍면동_영어"})\
        .to_json(orient="records")
    with open("../Data/lv3_korea_geo.json", "w") as outfile:
        outfile.write(
            json.dumps(json.loads(j_lv3),
                       indent=2,
                       sort_keys=False,
                       ensure_ascii=False)
        )

if __name__ == "__main__":
    kor_geo_data = get_data_from_csv()
    nested_sigungu_df = write_geo_lv2(kor_geo_data)
    write_geo_lv3(kor_geo_data)


import ast
import io
import os
import sys
import traceback
import types

from IPython.core.interactiveshell import InteractiveShell
from nbformat import read

from fklab.version._core_version._version import __version__

# from IPython import get_ipython

__all__ = ["install_notebook_loader"]

# code borrowed from:
# https://jupyter-notebook.readthedocs.io/en/stable/examples/Notebook/Importing%20Notebooks.html#

# with additional code filtering idea from:
# https://github.com/ipython/ipynb

def find_notebook(fullname, path=None):
    """find a notebook, given its fully qualified name and an optional path

    This turns "foo.bar" into "foo/bar.ipynb"
    and tries turning "Foo_Bar" into "Foo Bar" if Foo_Bar
    does not exist.
    """
    name = fullname.rsplit(".", 1)[-1]
    if not path:
        path = [""]
    for d in path:
        nb_path = os.path.join(d, name + ".ipynb")
        if os.path.isfile(nb_path):
            return nb_path
        # let import Notebook_Name find "Notebook Name.ipynb"
        nb_path = nb_path.replace("_", " ")
        if os.path.isfile(nb_path):
            return nb_path

ALLOWED_NODES = set([ast.ClassDef, ast.FunctionDef, ast.Import, ast.ImportFrom])

def filter_ast(module_ast):
    """
    Filters a given module ast, removing non-whitelisted nodes
    It allows only the following top level items:
     - imports
     - function definitions
     - class definitions
     - top level assignments where all the targets on the LHS are all caps
    """

    def node_predicate(node):
        """
        Return true if given node is whitelisted
        """
        for an in ALLOWED_NODES:
            if isinstance(node, an):
                return True

        # Recurse through Assign node LHS targets when an id is not specified,
        # otherwise check that the id is uppercase
        if isinstance(node, ast.Assign):
            return all(
                [node_predicate(t) for t in node.targets if not hasattr(t, "id")]
            ) and all(
                [
                    (t.id.isupper() and t.id[0] == "_")
                    for t in node.targets
                    if hasattr(t, "id")
                ]
            )

        return False

    module_ast.body = [n for n in module_ast.body if node_predicate(n)]
    return module_ast

class NotebookLoader(object):
    """Module Loader for Jupyter Notebooks"""

    def __init__(self, path=None, defs_only=False):
        self.shell = InteractiveShell.instance()
        self.path = path
        self.defs_only = defs_only

    def code_from_ipynb(self, nb, markdown=False):
        """
        Get the code for a given notebook
        nb is passed in as a dictionary that's a parsed ipynb file
        """
        code = ""
        for cell in nb.cells:
            if cell.cell_type == "code":
                # transform the input to executable Python
                code += "".join(
                    self.shell.input_transformer_manager.transform_cell(cell.source)
                )
                # code += ''.join(cell.source)
            if markdown and cell.cell_type == "markdown":
                code += "\n# " + "# ".join(cell.source)
            # We want a blank newline after each cell's output.
            # And the last line of source doesn't have a newline usually.
            code += "\n\n"
        return code

    def load_module(self, fullname):
        """import a notebook as a module"""
        path = find_notebook(fullname, self.path)

        # print ("importing Jupyter notebook from %s" % path)

        # load the notebook object
        with io.open(path, "r", encoding="utf-8") as f:
            nb = read(f, 4)

        # create the module and add it to sys.modules
        # if name in sys.modules:
        #    return sys.modules[name]
        mod = types.ModuleType(fullname)
        mod.__file__ = path
        mod.__loader__ = self
        # mod.__dict__['get_ipython'] = get_ipython
        sys.modules[fullname] = mod

        # extra work to ensure that magics that would affect the user_ns
        # actually affect the notebook module's ns
        save_user_ns = self.shell.user_ns
        self.shell.user_ns = mod.__dict__

        try:
            code = ast.parse(self.code_from_ipynb(nb))
            if self.defs_only:
                code = filter_ast(code)
            exec(compile(code, filename="<ast>", mode="exec"), mod.__dict__)
        finally:
            self.shell.user_ns = save_user_ns

        return mod

class NotebookFinder(object):
    """Module finder that locates Jupyter Notebooks"""

    def __init__(self, *args, **kwargs):
        self.loaders = {}
        self.args, self.kwargs = args, kwargs

    def find_module(self, fullname, path=None):
        nb_path = find_notebook(fullname, path)
        if not nb_path:
            return

        key = path
        if path:
            # lists aren't hashable
            key = os.path.sep.join(path)

        if key not in self.loaders:
            self.loaders[key] = NotebookLoader(path, *self.args, **self.kwargs)
        return self.loaders[key]

def install_notebook_loader(defs_only=True):
    sys.meta_path = [x for x in sys.meta_path if not isinstance(x, NotebookFinder)]
    sys.meta_path.append(NotebookFinder(defs_only=defs_only))


"""
Script to handle Zane related Math calculations

Arguments for mod: [Death Follows Close, Kill Skill Stacks, Active Action Skills, Movement Speed Over Base]

@author Prismatic
"""

def skillsSpec(skills, mods, gear):
    from utils import calcMain
    # Doubled Agent
    synchronicity = skills[0]
    donnybrook = skills[1]
    trick_of_the_light = skills[2]
    double_barrel = skills[3]

    # Hitman
    cold_bore = skills[4]
    violent_momentum = skills[5]
    death_follows_close = skills[6]

    # Under Cover
    confident_competence = skills[7]

    mods[0] = mods[0] + int(death_follows_close)
    normal_hit = [confident_competence, violent_momentum, synchronicity, donnybrook, double_barrel]

    normal = zane_normal_hit(normal_hit, mods) + gear[0]
    v1=0 + gear[1]
    v2=0 + gear[2]
    splash= 0 + gear[3]
    elemental = 0 + gear[4]
    critical = 0 + gear[5]
    bonus_cryo = (0.06 * int(cold_bore)) + (0.12 * int(trick_of_the_light)) + gear[6]

    mults = [normal, v1, v2, splash, elemental, critical, bonus_cryo]

    response, body, crit = calcMain.calc_Damage(mults)

    bonus_ele, body_1, crit_1 = calcMain.calc_bonus_elements(mults)
    if crit_1 != 0: response = response + "\n" + bonus_ele

    body = body + body_1
    crit = crit + crit_1

    response = response + "\n\nCombining Everything:\n**Peak Damage Multiplier:** " + str(round(body, 2)) + "\n**Peak Crit Damage Multiplier:** " + str(round(crit, 2))

    return response

"""
gun skills {confident_competence, violent_momentum, synchronicity, donnybrook, double_barrel}
skill modifiers {death_follows_close, kill_skill_stacks, active_action_skills, movement_speed_over_base}
"""
def zane_normal_hit(gun_skills, skill_modifiers):
    dfc = 1+0.25*float(skill_modifiers[0])
    return (0.35*float(gun_skills[0]))+(0.16*float(gun_skills[1])*skill_modifiers[3]*dfc)+(0.04*float(gun_skills[1])*dfc)+(0.04*float(gun_skills[2])*skill_modifiers[2])+(0.03*float(gun_skills[3])*dfc*skill_modifiers[1])+(0.25*float(gun_skills[4]))


from os.path import join
from os import makedirs, environ
from pathlib import Path
import logging
from dotenv import dotenv_values, load_dotenv
from io import StringIO

logging.basicConfig(level=logging.DEBUG)
LOG = logging.getLogger(__name__)
LOCAL_DIR = join(Path.home(), ".pygadgets")

def try_make_dirs(directory):
    try:
        makedirs(directory)
    except OSError:
        LOG.info(f"{directory} already exists. skipping.")
    except Exception as e:
        raise RuntimeError(f"unhandled exceptions {str(e)}")
    else:
        LOG.info(".pygadget dir created")

try_make_dirs(join(LOCAL_DIR, "data"))
try_make_dirs(join(LOCAL_DIR, "log"))

try:
    env = dotenv_values(stream=StringIO(open(join(LOCAL_DIR, ".env"), "r").read()))
    LOG.info("loaded vars from .env")
except OSError:
    env = {k: v for k, v in environ.items() if k.startswith("PYGADGETS")}
    LOG.info("loaded vars from environ")

if env == {}:
    raise RuntimeError("Unable to collect configuration vars")

config = type("Config", (), env)


import pytest

torch = pytest.importorskip(modname="torch", reason="torch is not installed")

from medkit.training import TrainerConfig, Trainer  # noqa: E402
from medkit.training.trainer import (
    OPTIMIZER_NAME,
    CONFIG_NAME,
    SCHEDULER_NAME,
)  # noqa: E402

from .dummy_context_component.dummy_corpus import DUMMY_DATASETS  # noqa: E402
from .dummy_context_component.dummy_component import (
    MockTrainableComponent,
)  # noqa: E402

class DummyMetricsComputer:
    """Mock accuracy metrics Computer with pre-determined values"""

    def __init__(self, minimize):
        self._epoch = 0
        # decrease during 3 epochs then overfit
        if minimize:
            self._scores = [1.0, 0.9, 0.8, 0.85]
        else:
            self._scores = [0.1, 0.2, 0.3, 0.25]

    def prepare_batch(self, model_output, input_batch):
        return {}

    def compute(self, all_data):
        score = self._scores[self._epoch]
        self._epoch += 1
        return {"accuracy": score}

def _get_trainer(
    output_dir, nb_epochs, minimize_metric, use_lr_scheduler, checkpoint_period
):
    mock_component = MockTrainableComponent()
    config = TrainerConfig(
        output_dir=output_dir,
        nb_training_epochs=nb_epochs,
        batch_size=1,
        checkpoint_period=checkpoint_period,
        checkpoint_metric="accuracy",
        minimize_checkpoint_metric=minimize_metric,
    )

    def lr_scheduler_builder(optimizer):
        return torch.optim.lr_scheduler.StepLR(optimizer, step_size=1, gamma=0.1)

    trainer = Trainer(
        mock_component,
        config=config,
        train_data=DUMMY_DATASETS["train"],
        eval_data=DUMMY_DATASETS["eval"],
        lr_scheduler_builder=lr_scheduler_builder if use_lr_scheduler else None,
        metrics_computer=DummyMetricsComputer(minimize=minimize_metric),
    )
    return trainer

def _check_checkpoint(path, use_lr_scheduler):
    expected_optimizer_file = path / OPTIMIZER_NAME
    expected_config_file = path / CONFIG_NAME
    expected_scheduler_file = path / SCHEDULER_NAME
    assert expected_optimizer_file.exists()
    assert expected_config_file.exists()
    if use_lr_scheduler:
        assert expected_scheduler_file.exists()
    else:
        assert not expected_scheduler_file.exists()

    # testing model checkpoint
    # this is related to trainable component
    MockTrainableComponent(model_path=path)

@pytest.mark.parametrize(
    "use_lr_scheduler,minimize_metric,overfit",
    [
        # no scheduler, minimize metric, overfit
        (False, True, True),
        # scheduler, minimize metric, overfit
        (True, True, True),
        # no scheduler, maximize metric, overfit
        (False, False, True),
        # only 3 epochs, no overfit (best model is also last model)
        (False, True, False),
    ],
    ids=["default", "with_lr_scheduler", "maximize_metric", "overfit"],
)
def test_trainer_checkpoint(tmp_path, use_lr_scheduler, minimize_metric, overfit):
    output_dir = tmp_path / "full_model"
    trainer = _get_trainer(
        output_dir=output_dir,
        nb_epochs=4 if overfit else 3,
        minimize_metric=minimize_metric,
        use_lr_scheduler=use_lr_scheduler,
        checkpoint_period=1,
    )
    trainer.train()

    checkpoints_paths = sorted(output_dir.iterdir())
    for path in checkpoints_paths:
        _check_checkpoint(path, use_lr_scheduler)

    # 2 checkpoints must be saved: the best and the last
    # unless they are the same
    if overfit:
        assert len(checkpoints_paths) == 2
        best_checkpoint, last_checkpoint = checkpoints_paths
        assert best_checkpoint.name.startswith("checkpoint_003")
        assert last_checkpoint.name.startswith("checkpoint_004")
    else:
        # when we don't overfit, the last is also the best
        assert len(checkpoints_paths) == 1
        assert checkpoints_paths[0].name.startswith("checkpoint_003")

@pytest.mark.parametrize("checkpoint_period", [0, 3, 4])
def test_checkpoint_period(tmp_path, checkpoint_period):
    output_dir = tmp_path / "full_model"
    trainer = _get_trainer(
        output_dir=output_dir,
        nb_epochs=4,
        minimize_metric=True,
        use_lr_scheduler=False,
        checkpoint_period=checkpoint_period,
    )
    trainer.train()

    checkpoints_paths = sorted(output_dir.iterdir())
    if checkpoint_period == 3:
        # epoch 3 was the best period and epoch 4 was the last
        # so both have checkpoints
        assert len(checkpoints_paths) == 2
        best_checkpoint, last_checkpoint = checkpoints_paths
        assert best_checkpoint.name.startswith("checkpoint_003")
        assert last_checkpoint.name.startswith("checkpoint_004")
    else:
        # checkpoint period was either 0 or 4, which has the same effect
        # since we have 4 periods
        # on both cases the only checkpoint saved is for the 4th epoch,
        # even though it's not the one with the best metric
        checkpoints_paths = sorted(output_dir.iterdir())
        assert len(checkpoints_paths) == 1
        assert checkpoints_paths[0].name.startswith("checkpoint_004")


import json
from twilio.rest import Client
from twilio.twiml.messaging_response import MessagingResponse
from twilio.twiml.voice_response import VoiceResponse
from flask import Flask, request, abort
from functools import wraps
import NumberList

app = Flask(__name__)

with open("API_Keys.json") as json_file:
    file = json.load(json_file)
    _account_sid = file["SID"]
    _auth_token = file["Token"]
    client = Client(_account_sid, _auth_token)

roster = "2020-2021 Roster.csv"
remind_message = "Hey {}, this is a reminder that your {} class starts in 10 mins at {} PM (PST). "\
                 "Please be sure to come on time. If you would like to opt out of these reminder " \
                 "messages, reply with 'STOP'."
oh_message = "Hey {}, this is a reminder that {} has office hours in 10 mins from {} (PST). Feel " \
             "free to drop by if you need any additional help. If you would like to opt out of " \
             "these reminder messages, reply with 'STOP'."

def validate_cron_request(f):
    """
    Validates that incoming requests genuinely originated from GAE Cron Scheduler
    """
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if request.headers.get('X-AppEngine-Cron') is None:
            app.logger.info("Error — Not sent from Cron Job")
            return abort(403)
        else:
            return f(*args, **kwargs)

    return decorated_function

@app.route("/text-reply", methods=["GET", "POST"])
def text_reply():
    response = MessagingResponse()
    form = "https://website.com/attendance"
    response.message(f"Sorry, staff does not receive replies to this "
                     f"number. If you are going to be absent, please "
                     f"fill out this form: {form}")
    return str(response)

@app.route("/voice-reply", methods=["GET", "POST"])
def phone_reply():
    response = VoiceResponse()
    response.say("Sorry, staff does not actively monitor this number for "
                 "calls. If you have any questions please email us. Thank you!",
                 voice='alice')
    return str(response)

@app.route("/send-test")
@validate_cron_request
def send_test():
    number_dict = {"Firstname Lastname": "+1234567890"}
    for person in number_dict:
        msg = client.messages.create(
            body=remind_message.format(person, "English", "3:00 AM"),
            from_="+1234567890",
            to=number_dict[person]
        )
        return msg.sid

@app.route("/send/language/<path:class_name>")
@validate_cron_request
def send_class_text(class_name):
    app.logger.info(class_name)
    number_dict = NumberList.get_lang_dict(csv=roster, class_name=class_name)
    for person in number_dict:
        class_title = person["Language Class"]
        language = class_title[0: class_title.find(":")]

        class_time = class_title.split()[-1]
        time = class_time[0: class_time.find("-")]
        msg = client.messages.create(
            body=remind_message.format(person["First Name"], language, time),
            from_="+1234567890",
            to=person["Phone Number"]
        )
        # print(remind_message.format(person["First Name"], language, time))
    return class_name

@app.route("/send/leadership/<path:cohort>")
@validate_cron_request
def send_leadership_text(cohort):
    time = "2:45" if cohort == "ARABIC" else "3:45"
    number_dict = NumberList.get_leadership_dict(csv=roster, leadership_cohort=cohort)
    for person in number_dict:
        msg = client.messages.create(
            body=remind_message.format(person["First Name"], "Leadership", time),
            from_="+1234567890",
            to=person["Phone Number"]
        )
    return cohort

@app.route("/send/office_hours/<path:teacher>")
@validate_cron_request
def send_office_hours_text(teacher):
    number_dict = NumberList.get_office_hours_dict(csv=roster, teacher=teacher)
    hours = {"Teacher1": "2:45 PM - 3:45 PM", "Teacher2": "11:00 AM - 12:00 PM",
             "Teacher3": "3:00 PM - 4:00 PM", "Teacher4": "12:00 PM - 1:00 PM ",
             "Teacher5": "5:20 PM - 5:50 PM", "Teacher6": "2:45 PM - 3:45 PM"}
    for person in number_dict:
        msg = client.messages.create(
            body=oh_message.format(person["First Name"], teacher, hours[teacher]),
            from_="+1234567890",
            to=person["Phone Number"]
        )
    return teacher

def main():
    print("Hello World")

if __name__ == '__main__':
    app.run(port=3000, debug=True)
    main()


from datetime import datetime
import numpy as np
import pandas as pd
import pandas_datareader as pdr
import os
from zipfile import ZipFile

def get_stocks(stock, start_dt, end_dt, id):
    DNE_list = []
    before_list = []

    if (end_dt-start_dt).days <= 0:
        return "Error", before_list

    stock_list = stock.split(',')
    with ZipFile('sampleDir%s.zip' % id, 'w') as zipObj:
        for stocks in stock_list:
            stocks = stocks.replace(" ", "")
            try:
                df = pdr.data.get_data_yahoo(stocks, start=start_dt, end=end_dt)

                """Check for date < start_dt"""
                time_str = str(df.index.values[0]).replace('T00:00:00.000000000', '')
                time = datetime.strptime(time_str, "%Y-%m-%d")
                if time.toordinal() > start_dt.toordinal()+7:
                    before_list.append(stocks)

                df.to_csv('%s.csv' % stocks)
                zipObj.write('%s.csv' % stocks)
            except:
                DNE_list.append(stocks)

    if dev:
        for stocks in stock_list:
            try:
                stocks = stocks.replace(" ", "")
                os.remove('%s.csv' % stocks)
            except:
                pass

    return DNE_list, before_list


import torch
import torch.nn as nn

# Task 5: Add FFN for Critic
class CriticNN(nn.Module):
    def __init__(self, input_size, output_size, hidden_size, n_layers):
        super(CriticNN, self).__init__()

        self.input_size = input_size
        self.output_size = output_size
        self.hidden_size = hidden_size
        self.n_layers = n_layers

        self.tanh = nn.Tanh()

        self.input_layer = nn.Linear(self.input_size, self.hidden_size)
        self.hidden_layer = nn.Linear(self.hidden_size, self.hidden_size)
        self.output_layer = nn.Linear(self.hidden_size, self.output_size)

    def forward(self, state):
        state = torch.as_tensor(state)

        network = self.tanh(self.input_layer(state))
        for i in range(self.n_layers):
            network = self.tanh(self.hidden_layer(network))
        network = self.output_layer(network)

        value = network.item()

        return value


"""
    Fondamentum by M. Virdis
"""
import quantopian.algorithm as algo
from quantopian.optimize import TargetWeights, NetExposure, PositionConcentration, MaximizeAlpha
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.data import Fundamentals
from quantopian.pipeline.data.morningstar import Fundamentals as MSFundamentals
from quantopian.pipeline.filters import QTradableStocksUS, Q500US, Q1500US
from scipy import stats
import numpy as np

def momentum_score(ts):
    """
    Input: security time series
    Output: annualized exp. regression slope * R2
    """
    # Create simple array [0, 1, 2, 3, ...] to use as x axis
    x = np.arange(len(ts))
    y = np.log(ts)

    slope,interc,r_val,p_val,std_err = stats.linregress(x,y)

    annual = (np.power(np.exp(slope),252)-1)*100

    return annual*(r_val**2)

def initialize(context):
    """
    Called once at the start of the algorithm.
    """
    # Rebalance
    algo.schedule_function(
        rebalance,
        algo.date_rules.month_end(), # SHOULD BE MONTH
        algo.time_rules.market_close(minutes=30)
    )

    # Record tracking variables at the end of each day.
    algo.schedule_function(
        record_vars,
        algo.date_rules.every_day(),
        algo.time_rules.market_close(minutes=15)
    )

    # Create our dynamic stock selector.
    algo.attach_pipeline(make_pipeline(), 'pipeline')

    # Set commissions
    algo.set_commission(commission.PerTrade(cost=19.0))
    #algo.set_commission(commission.NoCommission())
    #algo.set_commission(commission.PerShare(cost=.05))

    context.use_weights = True # If weights should be used in optimization

    context.filters = True         # use filtering?
    context.TF_lookback = 63       # How many days of SPY to look at
    context.can_buy_stocks = False # Triggered by bullish market
    context.can_buy = True

    context.bonds = [symbol('IEF'), symbol('SHY'), symbol('TLT')]

    context.num_stocks_to_trade = 20
    context.roe_top_n = 50      # How many top roe companies to keep
    context.momentum_days = 126 # How many days to check momentum
    context.score_to_go = 30    # Minimum Momentum score to consider a share
    context.days_to_skip = 10   # days before today to ignore in momentum

def make_pipeline():
    """
    A function to create our dynamic stock selector (pipeline). Documentation
    on pipeline can be found here:
    https://www.quantopian.com/help#pipeline-title
    """

    pipe = Pipeline(
        columns={'roe': Fundamentals.roe.latest,
                 'roic': Fundamentals.roic.latest,
                 'pb': MSFundamentals.pb_ratio.latest,
                 'cap': Fundamentals.market_cap.latest,
                 'close': USEquityPricing.close.latest},
        screen=Q1500US()
    )
    return pipe

def before_trading_start(context, data):
    """
    Called every day before market open.
    """
    # Safety checks--------

    if context.filters:
        # Check market conditions
        spy_hist = data.history(symbol('SPY'),'close',
                                context.TF_lookback+5,'1d')
        spy_momentum = spy_hist.pct_change(context.TF_lookback).iloc[-1]

        if spy_momentum > 0.0:
            context.can_buy_stocks = True
        else:
            context.can_buy_stocks = False
    else:
        context.can_buy = context.can_buy_stocks = True

def rebalance(context, data):
    """
    Execute orders according to our schedule_function() timing.
    """

    df = algo.pipeline_output('pipeline')

    # These are the securities that we are interested in trading each month.
    top_roe = df['roe'].nlargest(context.roe_top_n).index

    # Perform Momentum calculations for each security
    ranking = []
    for sec in top_roe:
        ts = data.history(sec,'price',context.momentum_days+context.days_to_skip+10,'1d')
        score = momentum_score(ts[:-context.days_to_skip])
        if score > context.score_to_go:
            ranking.append((score,sec))

    # Sort to rank each security based on momentum
    ranking.sort(reverse=True)

    sec_to_trade = [el[1] for el in ranking]
    sec_to_trade = sec_to_trade[:context.num_stocks_to_trade]

    # weights for next order
    weights = {}

    # First find positions to close
    for sec in context.portfolio.positions:
        if sec not in sec_to_trade:
            if sec not in context.bonds:
                weights[sec] = 0.0
            elif context.can_buy_stocks: # TODO Gradual decrease of bonds not all at once sold
                weights[sec] = 0.0

    # Compute remaining weights
    if context.can_buy_stocks and context.can_buy:

        for sec in sec_to_trade:
            weights[sec]=1.0/len(sec_to_trade)

    elif not context.can_buy_stocks and context.can_buy: # buy bonds

        cs = current_money_in_stocks(context)
        bond_weight = 1.0-(cs/context.portfolio.portfolio_value)

        for bond in context.bonds:
            if bond_weight > 0.0:
                weights[bond] = bond_weight/len(context.bonds)
            else:
                weights[bond] = 0.0

    if context.use_weights: # Use optimizer with weights
        objective = TargetWeights(weights)
        constraints = [
            NetExposure(0, 1.0)
        ]

        algo.order_optimal_portfolio(objective, constraints)

def current_money_in_stocks(context):
    ss = 0.0
    for sec in context.portfolio.positions:
        if sec in context.bonds:
            continue
        pos = context.portfolio.positions[sec]
        ss += pos.amount * pos.cost_basis

    return ss

def record_vars(context, data):
    """
    Plot variables at the end of each day.
    """
    ss = 0.0
    bs = 0.0
    for sec in context.portfolio.positions:
        pos = context.portfolio.positions[sec]
        if sec not in context.bonds:
            ss += pos.amount * pos.cost_basis
        else:
            bs += pos.amount * pos.cost_basis

    cash = context.portfolio.cash

    record(total_value=ss+cash+bs)
    record(stocks=ss)
    record(bonds=bs)
    record(cash=cash)


import multiprocessing as mp
import time
from os import getpid as pid

# A queue returns a process shared queue, is thread and process safe
class Producer(mp.Process):
    def __init__(self, q):
        mp.Process.__init__(self)
        self.q = q

    def run(self):
        for i in range(5):
            self.q.put(i)
            print("p-%s:%s" % (pid(), i))
            time.sleep(1)
        print('exit p-%s' % (pid(),))

class Consumer(mp.Process):
    def __init__(self, q):
        mp.Process.__init__(self)
        self.q = q

    def run(self):
        while not self.q.empty():
            print("c-%s:%s" % (pid(), self.q.get()))
            time.sleep(2)
        print('exit c-%s' % (pid(),))

queue = mp.Queue()
p_list = [Producer(queue), Producer(queue), Consumer(queue), Consumer(queue)]

for proc in p_list:
    proc.start()

for proc in p_list:
    proc.join()


# hint: 가능한 조합에서 중복이 발생하지 않는 경우 최소성을 만족하는지 확인, 문자열 in 은 덩어리를 하나로 인식함->set으로 판별

import itertools
def solution(arr):
    key=[]
    n=len(arr[0])   # 속성 수
    m=len(arr)      # 튜플수
    tmp=[]
    for i in range(1,n+1):
        tmp+=[*map(list,itertools.combinations(range(n),i))]
    for i in tmp:
        cand = "".join(map(str, i))
        flag = 1
        for p in key:
            if len(set(list(p))-set(list(cand)))==0:
                flag = 0
                break
        if flag:
            q = [""]*m
            for j in i:
                for k in range(m):
                    q[k]+=("-"+arr[k][j])
            if len(set(q)) == m:
                key +=[cand]
    return len(key)

arr=[["b","2","a","a","b"],["b","2","7","1","b"],["1","0","a","a","8"],["7","5","a","a","9"],["3","0","a","f","9"]]
print(solution(arr))

카카오/2019 블라인드/후보키.py


import setuptools

with open(".github/README.md", "r", encoding="utf-8") as fh:
    long_description = fh.read()

setuptools.setup(
    name="dqm",
    version="3.10.00",
    author="Sarah Schoenfeldt",
    author_email="Sarah.Schoenfeldt@cms.hhs.gov",
    description="A package to calculate data quality measures on T-MSIS data using Databricks",
    # long_description=long_description,
    # long_description_content_type="text/markdown",
    url="https://git-codecommit.us-east-2.amazonaws.com/v1/repos/DQ-Measures",
    classifiers=[
        "Programming Language :: Python :: 3.9.13 :: Only",
        "Operating System :: OS Independent",
    ],
    packages=setuptools.find_packages(),
    package_data={
        "dqm": ["cfg/*.pkl", "batch/*.pkl", "testing/*.pkl"],
    },
    project_urls={
        'Documentation': 'https://tmsis2.atlassian.net/wiki/spaces/DQM/pages/2758180868/Data+Quality+Measures+Python+Library',
        'Tracker': 'https://tmsis2.atlassian.net/browse/DQM',
    },
    python_requires=">=3.8",
)


import cv2
import numpy as np

def read_img(img):
    (_, thresh) = cv2.threshold(img, 150, 255, cv2.THRESH_BINARY)
    kernel_2 = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 2))    # 形态学处理:定义矩形结构
    closed_2 = cv2.erode(thresh, kernel_2, iterations=2)            # 闭运算：迭代2次

    kernel_5 = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))    # 形态学处理:定义矩形结构
    closed_5 = cv2.erode(thresh, kernel_5, iterations=5)            # 闭运算：迭代5次

    cv2.imshow('original_img', img)
    cv2.imshow("closed_2",closed_2)
    cv2.imshow("closed_5",closed_5)
    cv2.waitKey(0)
    return closed_2,closed_5

def project_img(image):
    height, width = image.shape[:2]
    print("image.shape",image.shape)

    # 垂直投影：统计并存储每一列的黑点数
    vertical = np.zeros(width,dtype=np.int32)
    for x in range(0, width):
        for y in range(0, height):
            if image[y, x] == 0:
                vertical[x]+=1

    # 水平投影  #统计每一行的黑点数
    horizontal = np.zeros(height,dtype=np.int32)
    for y in range(0, height):
        for x in range(0, width):
            if image[y, x] == 0:
                horizontal[y] += 1

    # 创建空白图片，绘制垂直投影图
    emptyImage = np.zeros((height, width, 3), np.uint8)
    for x in range(0, width):
        for y in range(0, vertical[x]):
            b = (255, 255, 255)
            emptyImage[y, x] = b

    # 绘制水平投影图
    emptyImage1 = np.zeros((height, width, 3), np.uint8)
    for y in range(0, height):
        for x in range(0, horizontal[y]):
            b = (255, 255, 255)
            emptyImage1[y, x] = b

    cv2.imshow('chuizhi', emptyImage)
    cv2.imshow('shuipin', emptyImage1)
    cv2.waitKey(0)

if __name__ == '__main__':
    img = cv2.imread('../img/wan.png')
    print("img",img.shape)
    gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    retval, threshold_img = cv2.threshold(gray_img, 200, 255, cv2.THRESH_TOZERO)
    retval, threshold_img = cv2.threshold(threshold_img, 200, 255, cv2.THRESH_BINARY)
    dilate_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (6, 8))

    # 获得胀化后的图像
    dilate_img = cv2.dilate(threshold_img, dilate_kernel)
    # closed_2, closed_5 = read_img(img)
    project_img(dilate_img)
    # project_img(closed_5)
    cv2.destroyAllWindows()


""" Manage subscription endpoint(s) """
from fastapi import Depends, Query, exceptions
from fastapi.responses import HTMLResponse

from marketing import models, utils
from marketing.main import app

@app.get("/unsubscribe")
def unsubscribe(
        contact_id: str = Query(..., alias="id"),
        session: models.Session = Depends(utils.get_session),
) -> HTMLResponse:
    """ Validate the user via the API key and return user info """
    contact = session.query(models.Contact).get(contact_id)
    if not contact:
        raise exceptions.HTTPException(400)

    contact.unsubscribe()
    models.Pending.delete_for_contact(
        session, contact_id=contact_id, commit=False
    )
    session.commit()

    return HTMLResponse(
        content=utils.render_template("unsubscribed", contact_id=contact_id)
    )

@app.get("/resubscribe")
def resubscribe(
        contact_id: str = Query(..., alias="id"),
        session: models.Session = Depends(utils.get_session),
) -> HTMLResponse:
    """ Validate the user via the API key and return user info """
    contact = session.query(models.Contact).get(contact_id)
    if not contact:
        raise exceptions.HTTPException(400)

    contact.resubscribe()
    session.commit()

    return HTMLResponse(
        content=utils.render_template("resubscribed", contact_id=contact_id)
    )