import os.path
import os
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from datetime import datetime

# If modifying these scopes, delete the file token.json.
SCOPES = ['https://www.googleapis.com/auth/drive']

def main():
    """Shows basic usage of the Drive v3 API.
    Prints the names and ids of the first 10 files the user has access to.
    """

    creds = None
    # The file token.json stores the user's access and refresh tokens, and is
    # created automatically when the authorization flow completes for the first
    # time.
    if os.path.exists('token.json'):
        creds = Credentials.from_authorized_user_file('token.json', SCOPES)
    # If there are no (valid) credentials available, let the user log in.
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            print("Refresh Creds")
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                'client_secrets.json', SCOPES)
            creds = flow.run_local_server(port=0)
        # Save the credentials for the next run
        with open('token.json', 'w') as token:
            token.write(creds.to_json())

    service = build('drive', 'v3', credentials=creds)

    # Call the Drive v3 API
    results = service.files().list(
        q="mimeType = 'application/vnd.google-apps.folder' and '0ALNhV0hP-QYDUk9PVA' in parents",
        pageSize=100, fields="nextPageToken, files(id, name, parents)").execute()
    items = results.get('files', [])

    pic_id = ''

    if not items:
        print('No files found.')
    else:
        print('1st Files:')
        for item in items:
            if item['name']=='KIOSK Picture':
                pic_id = item['id']
            print(u'{0} ({1}) - {2}'.format(item['name'], item['id'], item['parents']))
    #print(pic_id)

    # Check Machine ID
    q_str = "mimeType = 'application/vnd.google-apps.folder' and '" + str(pic_id) +"' in parents"
    #print(q_str)
    results = service.files().list(
        q=q_str, #"mimeType = 'application/vnd.google-apps.folder' and '" + str(pic_id) +"' in parents",
        pageSize=10, fields="nextPageToken, files(id, name, parents)").execute()
    items = results.get('files', [])

    bHasBaseFolder = False
    sMachineID = 'Test_MachineID'
    sMachineID_ID = ''
    if not items:
        print('No files found.')
    else:
        print('2nd Files:')
        for item in items:
            if item['name']==sMachineID:
                bHasBaseFolder = True
                sMachineID_ID = item['id']
            print(u'{0} ({1}) - {2}'.format(item['name'], item['id'], item['parents']))

    if bHasBaseFolder == False:
        file_metadata = {
            'name': sMachineID,
            'mimeType': 'application/vnd.google-apps.folder',
            'parents': [str(pic_id)]
        }
        file = service.files().create(body=file_metadata,
                                    fields='id').execute()
        sMachineID_ID = str(file.get('id'))
        print('Folder ID: %s' % file.get('id'))

    #print(sMachineID_ID)

    # Check Date Folder
    sTodayDateString = datetime.now().strftime("%Y%m%d")
    sTodayDate_ID = ''
    bHasBaseFolder = False
    q_str = "mimeType = 'application/vnd.google-apps.folder' and '" + str(sMachineID_ID) +"' in parents"
    results = service.files().list(
        q=q_str,
        pageSize=10, fields="nextPageToken, files(id, name, parents)").execute()
    items = results.get('files', [])
    if not items:
        print('No files found.')
    else:
        print('3nd Files:')
        for item in items:
            if item['name']==sTodayDateString:
                bHasBaseFolder = True
                sTodayDate_ID = item['id']
            print(u'{0} ({1}) - {2}'.format(item['name'], item['id'], item['parents']))
    if bHasBaseFolder == False:
        file_metadata = {
            'name': sTodayDateString,
            'mimeType': 'application/vnd.google-apps.folder',
            'parents': [str(sMachineID_ID)]
        }
        file = service.files().create(body=file_metadata,
                                    fields='id').execute()
        sTodayDate_ID = str(file.get('id'))
        print('Folder ID: %s' % file.get('id'))

    #Check Test Location
    sTestLocation='我是測試考場(真的是測試用)'
    sTestLocation_ID = ''
    bHasBaseFolder = False
    q_str = "mimeType = 'application/vnd.google-apps.folder' and '" + str(sTodayDate_ID) +"' in parents"
    results = service.files().list(
        q=q_str,
        pageSize=10, fields="nextPageToken, files(id, name, parents)").execute()
    items = results.get('files', [])
    if not items:
        print('No files found.')
    else:
        print('4nd Files:')
        for item in items:
            if item['name']==sTestLocation:
                bHasBaseFolder = True
                sTestLocation_ID = item['id']
            print(u'{0} ({1}) - {2}'.format(item['name'], item['id'], item['parents']))
    if bHasBaseFolder == False:
        file_metadata = {
            'name': sTestLocation,
            'mimeType': 'application/vnd.google-apps.folder',
            'parents': [str(sTodayDate_ID)]
        }
        file = service.files().create(body=file_metadata,
                                    fields='id').execute()
        sTestLocation_ID = str(file.get('id'))
        print('Folder ID: %s' % file.get('id'))

    sTestLocation_ID = CreateGoogleDriveFolder(service, sTestLocation, sTodayDate_ID)
    print('Check Function')
    print(sTestLocation_ID)

def CreateGoogleDriveFolder(service, titlestring, folderid):
    returnfolderid=''

    bHasFolder = False
    q_str = "mimeType = 'application/vnd.google-apps.folder' and '" + str(folderid) +"' in parents"
    results = service.files().list(
        q=q_str,
        pageSize=10, fields="nextPageToken, files(id, name, parents)").execute()
    items = results.get('files', [])
    if not items:
        print('No files found.')
    else:
        for item in items:
            if item['name']==titlestring:
                bHasFolder = True
                returnfolderid = item['id']
            print(u'{0} ({1}) - {2}'.format(item['name'], item['id'], item['parents']))
    if bHasFolder == False:
        try:
            file_metadata = {
                'name': titlestring,
                'mimeType': 'application/vnd.google-apps.folder',
                'parents': [str(folderid)]
            }
            file = service.files().create(body=file_metadata,
                                        fields='id').execute()
            returnfolderid = str(file.get('id'))
            print('Folder ID: %s' % file.get('id'))
        except Exception as ex:
            print(ex)

    return returnfolderid

if __name__ == '__main__':
    main()


# 1부터 N까지 번호가 적힌 구슬이 있습니다.
# 이 중 중복을 허락하여 M번을 뽑아 일렬로 나열 하는 방법을 모두 출력합니다.

def DFS(L):
    global cnt
    if L==m:
        for i in range(m):
            print(res[i], end=' ')
        print()
        cnt+=1
    else:
        for i in range(1, n+1):
            res[L]=i
            DFS(L+1)

if __name__=="__main__":
    n, m=map(int, input().split())
    res=[0]*n
    cnt=0
    DFS(0)
    print(cnt)


"""
Main script to fit and predict the label (classes) of the blobs
"""
import warnings
from pathlib import Path

import pytorch_lightning as pl

from zero_to_hero.config_reader import read_config
from zero_to_hero.data.blobs import BlobsDataModule
from zero_to_hero.models.blobs_classifier import BlobsClassifierModel
from zero_to_hero.trainer.blobs_with_callbacks import get_blobs_trainer_with_callbacks

warnings.filterwarnings("ignore")

def main() -> None:
    """
    Main function that runs the fit (training) of the blobs' classifier and its predictions
    :return:
    """
    pl.seed_everything(
        seed=1,
        workers=True,
    )

    config = read_config(path=Path("configs/blobs.yml"))

    datamodule = BlobsDataModule(config=config)
    model = BlobsClassifierModel(config=config)

    trainer = get_blobs_trainer_with_callbacks(config=config)

    trainer.fit(
        model=model,
        datamodule=datamodule,
    )
    print("Best checkpoint path:", trainer.checkpoint_callback.best_model_path)

    trainer.test(
        model=model,
        datamodule=datamodule,
        ckpt_path="best",
        verbose=True,
    )

    predictions_probabilities = trainer.predict(
        model=model,
        datamodule=datamodule,
        return_predictions=True,
        ckpt_path="best",
    )
    print(predictions_probabilities)

if __name__ == "__main__":
    main()


""" Constants module: Usefull constants computation functions for gradient
descent algorithm. """
# Authors: Hamza Cherkaoui <hamza.cherkaoui@inria.fr>
# License: BSD (3-clause)

import numpy as np
import numba

from .atlas import get_indices_from_roi

def _precompute_sum_ztz_sum_ztz_y(uz_roi, X_roi, n_times_atom, factor):
    """ Precompute usefull constants for _grad_v_scaled_hrf

    Parameters
    ----------
    X_roi : array, shape (n_voxels_roi, n_times), fMRI data
    uz_roi : array, shape (n_voxels_roi, n_times_valid), neural activty signals

    Return
    ------
    sum_ztz : array, shape (2 * n_times_valid - 1,), sum of auto-convolution
        for each temporal activation (for each voxels)
    sum_ztz_y : array or None, shape (n_times_atom,), sum of auto-convolution
        for each temporal activation convolved with x_j (for each voxels)
    """
    n_voxels_roi, n_times_valid = uz_roi.shape
    sum_ztz = np.zeros((2 * n_times_atom - 1,))
    sum_ztz_y = np.zeros((n_times_atom,))
    uz_roi_padded = np.zeros(2 * (n_times_atom - 1) + n_times_valid)
    for j in range(n_voxels_roi):
        _indices = slice(n_times_atom - 1, n_times_atom - 1 + n_times_valid)
        uz_roi_padded[_indices] = uz_roi[j, :]
        sum_ztz += np.convolve(uz_roi[j, :][::-1], uz_roi_padded, 'valid')
        # In case we try to recover the cost-function from the gradient,
        # the formula needs a '2 *' before 'the X_roi[j, :]', thus the factor
        # option (equal to 1.0 in case of gradient compuation)
        sum_ztz_y += np.convolve(uz_roi[j, :][::-1], factor * X_roi[j, :],
                                 'valid')
    return sum_ztz, sum_ztz_y

@numba.jit((numba.float64[:, :], numba.float64[:, :], numba.int64[:, :]),
           nopython=True, cache=True, fastmath=True)
def _precompute_uvtuv(u, v, rois_idx):  # pragma: no cover
    """ Pre-compute uvtuv.

    Parameters
    ----------
    u : array, shape (n_atoms, n_voxels), spatial maps
    v : array, shape (n_hrf_rois, n_times_atom), HRFs
    rois_idx: array, shape (n_hrf_rois, max_indices_per_rois), HRF ROIs

    Return
    ------
    uvtuv : array, shape (n_atoms, n_atoms, 2 * n_times_atom - 1), precomputed
        operator
    """
    n_atoms, n_voxels = u.shape
    _, n_times_atom = v.shape
    vtv = np.empty((n_voxels, 2 * n_times_atom - 1))
    for m in range(rois_idx.shape[0]):
        indices = get_indices_from_roi(m, rois_idx)
        vtv[indices, :] = np.convolve(v[m, :], v[m, ::-1])
    uvtuv = np.zeros((n_atoms, n_atoms, 2 * n_times_atom - 1))
    for k0 in range(n_atoms):
        for k in range(n_atoms):
            _sum = u[k0, 0] * u[k, 0] * vtv[0]
            for j in range(1, n_voxels):
                _sum += u[k0, j] * u[k, j] * vtv[j, :]
            uvtuv[k0, k, :] = _sum
    return uvtuv

def _precompute_B_C(X, z, H, rois_idx):
    """ Compute list of B, C from givem H (and X, z).

    Parameters
    ----------
    X : array, shape (n_voxels, n_times), fMRI data
    z : array, shape (n_atoms, n_times_valid), temporal components
    H : array, shape (n_hrf_rois, n_times_valid, n_times), Toeplitz matrices
    rois_idx: array, shape (n_hrf_rois, max_indices_per_rois),

    Return
    ------
    B : array, shape (n_atoms, n_voxels), precomputed operator
    C : array, shape (n_atoms, n_atoms), precomputed operator
    """
    n_hrf_rois, _ = rois_idx.shape
    n_voxels, _ = X.shape
    n_atoms, _ = z.shape
    B = np.empty((n_hrf_rois, n_atoms, n_voxels))
    C = np.empty((n_hrf_rois, n_atoms, n_atoms))
    for m in range(n_hrf_rois):
        indices = get_indices_from_roi(m, rois_idx)
        H_m = H[m, :, :]
        z_H_m_t = z.dot(H_m.T)
        C[m, :, :] = z_H_m_t.dot(z_H_m_t.T)
        B[m, :, indices] = X[indices, :].dot(z_H_m_t.T)
    return B, C

@numba.jit((numba.float64[:, :], numba.float64[:, :], numba.float64[:, :, :]),
           nopython=True, cache=True, fastmath=True)
def _precompute_d_basis_constant(X, uz, H):  # pragma: no cover
    """ Precompute AtA and AtX.

    Parameters
    ----------
    X : array, shape (n_voxels, n_times), fMRI data
    uz : array, shape (n_voxels, n_times_valid), neural activity
    H : array, shape (n_hrf_rois, n_times_valid, n_times), Toeplitz matrices

    Return
    ------
    AtA : array, shape (n_atoms_hrf, n_atoms_hrf), precomputed operator
    AtX : array, shape (n_atoms_hrf), precomputed operator
    """
    n_voxels_rois, n_times = X.shape
    n_atoms_hrf, _, _ = H.shape
    A_d = np.empty((n_atoms_hrf, n_voxels_rois * n_times))
    AtX = np.empty(n_atoms_hrf)
    AtA = np.empty((n_atoms_hrf, n_atoms_hrf))
    for d in range(n_atoms_hrf):
        A_d[d, :] = uz.dot(H[d, :, :].T).ravel()
    X_ravel = X.ravel()
    for d in range(n_atoms_hrf):
        AtX[d] = A_d[d, :].dot(X_ravel)
        for d1 in range(d, n_atoms_hrf):
            AtA[d, d1] = AtA[d1, d] = A_d[d, :].dot(A_d[d1, :])
    return AtA, AtX


from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import numpy as np
import os

import tensorflow as tf
import tqdm
from absl import app
from absl import flags

from data.common import MODEL_DIR, get_pretrained_word_embedding
from data.mscoco import load_mscoco_data, DATA_DIR
from membership.utils import compute_adversarial_advantage
from text_encoder import ImageTextEncoder

flags.DEFINE_integer('print_every', 100, 'Print loss every N iterations')
flags.DEFINE_integer('batch_size', 512, 'Batch size')
flags.DEFINE_integer('encoder_dim', 512, 'encoder dim')
flags.DEFINE_integer('epoch', 100, 'Epochs of training')
flags.DEFINE_boolean('contrastive', False, 'Use vse++ contrastive loss')
flags.DEFINE_float('train_size', 0.1, 'Temp for sharpening')
flags.DEFINE_string('metric', 'cosine', 'Metric to use for membership')
flags.DEFINE_string('model', 'linear', 'Attack model')
flags.DEFINE_string('encoder_type', 'rnn', 'Text encoder type')
flags.DEFINE_string('image_model_name', 'resnet',
                    'Image feature extractor name')
flags.DEFINE_string('save_dir', os.path.join(MODEL_DIR, 'imgtxt'),
                    'Model directory for embedding model')
FLAGS = flags.FLAGS

def collect_metrics(img_embs, text_embs, img_id_text_ids):
  metrics = []
  for img_id in tqdm.tqdm(img_id_text_ids):
    text_ids = img_id_text_ids[img_id]
    i_emb = img_embs[img_id]
    t_embs = text_embs[text_ids]
    if FLAGS.metric == 'l2':
      scores = [-np.linalg.norm(i_emb - t_emb) for t_emb in t_embs]
    elif FLAGS.metric == 'cosine':
      scores = [np.dot(i_emb, t_emb) for t_emb in t_embs]
    else:
      raise ValueError(FLAGS.metrics)

    metrics.append(scores)
  return metrics

def encode_image_text_data():
  train_data, val_data, vocab = load_mscoco_data(FLAGS.image_model_name)
  word_embedding = get_pretrained_word_embedding(
      vocab, glove=False, vocab_include_pad=True,
      save_path=DATA_DIR + 'w2v.npz')
  encoder = ImageTextEncoder(word_embedding, FLAGS.encoder_dim,
                             FLAGS.encoder_type)

  model_name = 'coco_e{}_b{}_{}'.format(
    FLAGS.epoch, FLAGS.batch_size, FLAGS.encoder_type)
  if FLAGS.contrastive:
    model_name += '_vse'

  save_path = os.path.join(FLAGS.save_dir, model_name)

  encoder.load_weights(save_path)
  train_img_embs, train_text_embs, train_img_id_text_ids = \
      encoder.encode(train_data)
  test_img_embs, test_text_embs, test_img_id_text_ids = \
      encoder.encode(val_data)

  train_data = train_img_embs, train_text_embs, train_img_id_text_ids
  test_data = test_img_embs, test_text_embs, test_img_id_text_ids
  tf.keras.backend.clear_session()
  return train_data, test_data

def main(_):
  train_data, test_data = encode_image_text_data()
  train_metrics = collect_metrics(*train_data)
  test_metrics = collect_metrics(*test_data)
  compute_adversarial_advantage([np.mean(m) for m in train_metrics],
                                [np.mean(m) for m in test_metrics])

if __name__ == '__main__':
  app.run(main)


exp_needed = float(input())
battles = int(input())
exp_gain = 0
count = 0
for i in range(1, battles + 1):
    count += 1
    gain = float(input())
    exp_gain += gain
    if i % 3 == 0:
        exp_gain += gain * 0.15
    if i % 5 == 0:
        exp_gain -= gain * 0.1
    if i % 15 == 0:
        exp_gain += gain * 0.05
    if exp_gain >= exp_needed:
        break
if exp_gain >= exp_needed:
    print(f'Player successfully collected his needed experience for {count} battles.')
else:
    print(f'Player was not able to collect the needed experience, {(exp_needed - exp_gain):.2f} more needed.')

Python Fundamentals/10. Mid exams/Mid exam/Expirience gaining.py


""" Bout utility methods

Methods for extracting bouts from DataFrames and annotating other DataFrames
with this bout information.

A bout is a time range within a larger set of data that shares a particular feature.
"""

import pandas as pd

def extract_bouts(
    df, valid, range_column="t", valid_column="valid", keep_invalid=True, by=[]
):
    """
    Extract from a Pandas DataFrame a list of bouts, where each bout is indicated by a minimum and maximum
    timestamp range and determined by valid ranges.

    Parameters
    ----------
    df : pandas.Dataframe
        The data to extract the bouts from
    valid : pandas.Series
        A series of bool values indicating what rows are considered valid and invalid
    range_column : str
        Optional string indicating the column in df for the timestamp
    valid_column : str
        Optional string indicating the name in the output DataFrame indicating the validness of the bout
    keep_invalid : bool
        Optional value indicating whether to keep invalid bouts in the returned DataFrame
    by : list
        Optional list of columns to group the data by, before extracting the bouts, meaning that bout
        boundaries are both determined by the valid column and the group boundaries.

    Returns
    -------
    pandas.DataFrame
        Index:
            RangeIndex
        Columns:
            Name: count, dtype: int64
                Number of rows in df belonging to the bout
            Name: t_start, dtype: datetime64[ns]
                Starting timestamp of the bout (t_ prefix depends on range_column)
            Name: t_end, dtype: datetime64[ns]
                End timestamp of the bout (t_ prefix depends on range_column)
            Name: valid, dtype: bool
                Whether the bout is valid according to given criterium
    """
    df["_filter"] = valid.loc[df.index]

    dfBool = valid != valid.shift()
    dfCumsum = dfBool.cumsum()

    by_list = [dfCumsum]
    for b in by:
        by_list.append(b)

    groups = df.groupby(by=by_list, sort=False)

    bouts = groups.agg({range_column: ["count", "min", "max"], "_filter": ["first"]})
    bouts.columns = [
        "count",
        range_column + "_start",
        range_column + "_end",
        valid_column,
    ]

    df.drop(columns=["_filter"], inplace=True)

    if not keep_invalid:
        bouts = bouts[bouts[valid_column]]

    bouts.reset_index(drop=True, inplace=True)

    return bouts

def with_padded_bout_window(bouts, window=[0, 0], range_column="t"):
    """
    Pad the values in a Pandas DataFrame created with `extract_bouts` with a time window.

    Parameters
    ----------
    bouts : pandas.Dataframe
        The DataFrame containing the bouts
    window : list
        The number of seconds to add to the starting and end time of the bout.
    range_column : str
        Optional string indicating the column in original for the timestamp. This results in a prefix
        in the bouts DataFrame, timestamp column 't' leads to bout columns 't_min' and 't_max'.

    Returns
    -------
    pandas.DataFrame
        A copy of the bouts DataFrame with padded min and max timestamp values
    """
    bouts = bouts.copy()
    bouts[range_column + "_start"] = bouts[range_column + "_start"] + pd.to_timedelta(
        window[0], unit="s"
    )
    bouts[range_column + "_end"] = bouts[range_column + "_end"] + pd.to_timedelta(
        window[1], unit="s"
    )

    return bouts

def add_bouts_as_column(
    df,
    bouts,
    new_column="bout",
    range_column="t",
    valid_column="valid",
    value="column",
    reset_value=pd.Series(dtype="float64"),
):
    """
    Applies the time ranges in a bouts DataFrame created with `extract_bouts` to the rows in another DataFrame, by
    adding bout data to a new column.

    Parameters
    ----------
    df : pandas.DataFrame
        The DataFrame containing the data that has to be annotated by bout information
    bouts : pandas.Dataframe
        The DataFrame containing the bouts
    new_column : str
        The optional column name to add with bout information
    range_column : str
        Optional string indicating the column in original for the timestamp. This results in a prefix
        in the bouts DataFrame, timestamp column 't' leads to bout columns 't_start' and 't_end'.
    valid_column : str
        Optional string indicating the name in the output DataFrame indicating the validness of the bout
    value : object
        Optional value to insert for a valid bout. If 'index' it takes the bout index as bout identifier,
        'column' fills in the valid column else sets the constant value given.
    reset_value : object
        Optional default value set to the new bouts column if it does not yet exist

    Returns
    -------
    pandas.DataFrame
        A reference to the updated df DataFrame. The original DataFrame is updated in place.
    """
    if new_column not in df:
        df[new_column] = reset_value

    for idx, bout in bouts.iterrows():
        df.loc[
            (df[range_column] >= bout[range_column + "_start"])
            & (df[range_column] < bout[range_column + "_end"]),
            new_column,
        ] = (
            bout[valid_column]
            if value == "column"
            else idx
            if value == "index"
            else value
        )

    return df

def interpolate_bouts_as_column(
    df,
    df_values,
    bouts,
    new_column="bout",
    range_column="t",
    valid_column="valid",
    value_column="position",
    reset_value=pd.Series(dtype="float64"),
):
    """
    Applies the time ranges in a bouts DataFrame created with `extract_bouts` to the rows in another DataFrame, by
    adding bout data to a new column.

    Parameters
    ----------
    df : pandas.DataFrame
        The DataFrame containing the data that has to be annotated by bout information
    bouts : pandas.Dataframe
        The DataFrame containing the bouts
    df_values : pandas.DataFrame
        The DataFrame containing the values to interpolate into df
    value_column : str
        The column in df_values with the values to interpolate into df
    new_column : str
        The optional column name to add with bout information
    range_column : str
        Optional string indicating the column in original for the timestamp. This results in a prefix
        in the bouts DataFrame, timestamp column 't' leads to bout columns 't_start' and 't_end'.
    valid_column : str
        Optional string indicating the name in the output DataFrame indicating the validness of the bout
    reset_value : object
        Optional default value set to the new bouts column if it does not yet exist

    Returns
    -------
    pandas.DataFrame
        A reference to the updated df DataFrame. The original DataFrame is updated in place.
    """

    if new_column not in df:
        df[new_column] = reset_value

    for idx, bout in bouts.iterrows():
        bout_data = df[
            (df[range_column] >= bout[range_column + "_start"])
            & (df[range_column] < bout[range_column + "_end"])
        ]

        t_data = pd.to_numeric(bout_data[range_column])
        t_bout, val_bout = (
            pd.to_numeric(df_values[range_column]),
            df_values[value_column],
        )

        if len(t_bout) > 1:
            from scipy import interpolate

            f = interpolate.interp1d(t_bout, val_bout, fill_value="extrapolate")
            val_data = [f(t) for t in t_data]
            df[new_column].iloc[bout_data.index] = val_data
        else:
            val_data = [
                val_bout[0] + (t - t_bout) / 1e9 for t in t_data
            ]  # manual extrapolation from a single point assuming ns precision in timestamps
            df[new_column].iloc[bout_data.index] = val_data

    # changes the df inplace but return dataframe for chaining purposes
    return df

def select_range(df, window, range_column="t", include_end=True):
    """
    Select a range of data for multiple Pandas DataFrames at once.
    It was designed for using it with the output of the bout methods.

    Parameters
    ----------
    df : pandas.DataFrame/list
        The (list of) DataFrame to use
    window : list
        A 2-list containing the minimum and maximum value of the range to select
    range_column : str
        Optional column in the DataFrame to use for the range (usually timestamp)
    include_end : bool
        Optional whether to include the max value in the range

    Returns
    -------
    list(pandas.DataFrame)
        A list of DataFrames selections
    """

    if not isinstance(df, list):
        df = [df]

    return [
        dff[(dff[range_column] >= window[0]) & (dff[range_column] <= window[1])]
        if include_end
        else dff[(dff[range_column] >= window[0]) & (dff[range_column] < window[1])]
        for dff in df
    ]


import sys
import os
from fileHelper import *
import difflib
import re

def main():
    """Runs independently to check data, refine data and save data to a new clean .tsv file"""
    for arg in sys.argv[1:]:
        data = readFile(arg)
        if data is not None:
            checkData(data)
            data = refineData(data)
            saveData(arg, data)

def saveData(dataName, data):
    """
    Saves the data to file
    """
    data.to_csv(dataName + "Clean.tsv", sep="\t", index=False)

def checkData(data):
    """
    Checks data is of valid format.
    """
    x = checkNumberOfCols(data)
    x = x and checkColNames(data)
    x = x and checkTypes(data)
    if not x:
        print("Data has inconsistencies, trying to run analysis may result in unexpected errors")
        sys.exit(1)

def checkColNames(data):
    """
    Checks column names.
    """
    col_names = ['page_id', 'page_title', 'rev_id', 'timestamp', 'type', 'id']
    x = True
    for name in col_names:
        if not (name in data.columns):
            print(name + " column is not in table")
            x = False
    return x

def checkNumberOfCols(data):
    """
    Checks number of columns.
    """
    if data.keys().shape[0] == 6:
        return True
    print("Invalid number of columns")
    return False

def checkTypes(data):
    """
    Checks each column has proper type.
    """
    x = True
    col_names = ['page_id', 'page_title', 'rev_id', 'timestamp', 'type', 'id']
    possible_values_for_type = ["doi", "isbn", "pmid", "pmc", "arxiv"]
    for name in col_names:
        if name == 'page_id' or name == 'rev_id':
            if data.dtypes[name] != "int64":
                print("Column \"" + name + "\" contains Invalid Type")
                x = False

        elif name == 'timestamp':
            if data.dtypes[name] != "datetime64[ns, UTC]":
                print("Column \"" + name + "\" contains Invalid Type")
                x = False

        elif name == 'type':
            for value in data[name].unique():
                if not (value in possible_values_for_type):
                    x = False
                    print("Column \"" + name + "\" contains Invalid Value")
        else:
            if data.dtypes[name] != "object":
                print("Column \"" + name + "\" contains Invalid Type")
                x = False
    return x

def refineData(data):
    """
    Calls fns to refine data.
    """
    data = checkTypeIdMismatch(data)
    data = handleEmptyValues(data)
    data = removeDuplicates(data)
    return data

def checkTypeIdMismatch(data):
    """
    Calls fns to check for type id mismatch.
    """
    # possible_values_for_type = ["doi", "isbn", "pmid", "pmc", "arxiv"]
    data = data.reset_index(drop=True)
    data = checkPMIDMismatch(data)
    data = checkISBNMismatch(data)
    return data

def checkPMIDMismatch(data):
    """
    Checks for PMID mismatch and removes any.
    """
    num_rows = data.shape[0]
    rows_with_pmid = data[data.type == 'pmid']
    rows_id_not_valid = rows_with_pmid[~rows_with_pmid.id.str.match("^(0{0,}\d{1,8})$")]
    data.drop(rows_id_not_valid.index, inplace=True)
    print(str(num_rows - data.shape[0]) + " Lines with mismatched pmid removed")
    return data

def checkISBNMismatch(data):
    """
    Checks for isbn mismatch and removes any.
    """
    num_rows = data.shape[0]
    rows_with_pmid = data[data.type == 'isbn']
    rows_id_not_valid = rows_with_pmid[~rows_with_pmid.id.str.match("^(97(8|9))?\d{9}(\d|X)$")]
    data.drop(rows_id_not_valid.index, inplace=True)
    print(str(num_rows - data.shape[0]) + " Lines with mismatched isbn removed")
    return data

def handleEmptyValues(data):
    """
    Drops rows with empty values
    """
    num_of_empty_in_col = data.isnull().sum()
    num_of_empty_in_col = num_of_empty_in_col[num_of_empty_in_col != 0]
    if not num_of_empty_in_col.empty:
        print("The following columns have empty fields")
        print(num_of_empty_in_col)
        data = data.dropna()
    return data

def removeDuplicates(data):
    """
    Removes any duplicates from the data. Displays the number of duplicate lines that were removed.
    """
    data_rows = data.shape[0]
    data = data.drop_duplicates()
    print("There are " + str(data_rows - data.shape[0]) + " Duplicate Rows in the Data")
    return data

if __name__ == '__main__':
    main()


import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from util import ecdf

def relationship_between_binomial_and_poisson_distribution():
    samples_poisson = np.random.poisson(10, size=10000)
    print('Poisson:     ', np.mean(samples_poisson), np.std(samples_poisson))

    n = [20, 100, 1000]
    p = [0.5, 0.1, 0.01]

    for i in range(3):
        samples_binomial = np.random.binomial(n[i], p[i], size=10000)
        mean = np.mean(samples_binomial)
        std = np.std(samples_binomial)
        print(f'n = {i} Binomial: {mean} {std}')

def relationship_between_binomial_and_poisson_distribution2():
    samples_poisson = np.random.poisson(2.5, size=10000)
    print('Poisson:     ', np.mean(samples_poisson), np.std(samples_poisson))

    # poisson distribution can approximate binomial distribution if n > 20 and p <= 0.05
    n = [5, 10, 20, 100]
    p = [0.5, 0.25, 0.125, 0.025]

    for i in range(4):
        samples_binomial = np.random.binomial(n[i], p[i], size=10000)
        mean = np.mean(samples_binomial)
        std = np.std(samples_binomial)
        print(f'n = {i} Binomial: {mean} {std}')

# No-hitter is a rare event in the game of baseball. There has been only 251 no-hitter games
# through 1901 to 2015 over 200,000 games.

def was_2015_anomalous():
    # There are, on average 255/115 no-hitter games per season
    n_nohitters = np.random.poisson(251/115, size=10000)
    n_large = np.sum(n_nohitters >= 7)
    p_large = n_large / 10000
    print('Probability of seven or more no-hitters:', p_large)

# relationship_between_binomial_and_poisson_distribution()
# relationship_between_binomial_and_poisson_distribution2()
was_2015_anomalous()


from direct.showbase.DirectObject import DirectObject
import panda3d.core as p3d

from .mapgen.dungeon import Dungeon
from .rangeindicator import RangeIndicator

class GameState(DirectObject):
    def __init__(self):
        super().__init__()
        self.root_node = p3d.NodePath('State Root')
        self.root_node.reparent_to(base.render)

    def cleanup(self):
        self.ignoreAll()
        self.root_node.remove_node()
        self.root_node = None

    def update(self, dt):
        pass

class MainState(GameState):
    PLAYER_SPEED = 15
    CAM_MOVE_BORDER = 0.8
    CAM_MOVE_SPEED = 50

    DUNGEON_SX = 50
    DUNGEON_SY = 50

    def __init__(self):
        super().__init__()

        self.accept('toggle-debug-cam', self.toggle_debug_cam)
        self.accept('move', self.move_player)
        self.accept('ability1', self.toggle_range, [0])
        self.accept('ability2', self.toggle_range, [1])
        self.accept('ability3', self.toggle_range, [2])
        self.accept('ability4', self.toggle_range, [3])

        self.mapgen = 'static'
        dungeon = Dungeon(self.mapgen, self.DUNGEON_SX, self.DUNGEON_SY)
        dungeon.model_root.reparent_to(self.root_node)

        dlight = p3d.DirectionalLight('sun')
        dlight.set_color(p3d.LVector3(0.2, 0.2, 0.2))
        dlight.set_shadow_caster(True, 4096, 4096)
        dlnp = self.root_node.attach_new_node(dlight)
        dlnp.set_z(10)
        dlnp.set_p(-90)
        lens = dlight.get_lens()
        lens.set_film_size(60)
        lens.set_near(1)
        lens.set_far(100)
        self.root_node.set_light(dlnp)

        dlight2 = p3d.DirectionalLight('ground')
        dlight2.set_color(p3d.LVector3(0.1, 0.1, 0.1))
        dlnp2 = self.root_node.attach_new_node(dlight2)
        self.root_node.set_light(dlnp2)

        loader = p3d.Loader.get_global_ptr()
        player = p3d.NodePath(loader.load_sync('dungeon.bam')).find('**/MonsterSpawn').node()
        playernp = self.root_node.attach_new_node(player)
        playernp.set_pos(dungeon.player_start)
        playernp.set_z(1.5)
        self.player_ranges = [
            RangeIndicator('box', length=5, width=1),
            RangeIndicator('circle', radius=2),
            RangeIndicator('circle', radius=3),
            RangeIndicator('circle', radius=4),
        ]
        for rangeindicator in self.player_ranges:
            rangeindicator.graphics.reparent_to(playernp)
            rangeindicator.visible = False

        # self.root_node.ls()
        # self.root_node.analyze()

        self.dungeons = [dungeon]
        self.dungeon_idx = 0
        self.dungeon = dungeon
        self.player = playernp
        self.last_tele_loc = None
        self.target = self.player.get_pos()
        self.debug_cam = False
        self.reset_camera()

    def toggle_debug_cam(self):
        if not self.debug_cam:
            mat = p3d.LMatrix4(base.camera.get_mat())
            mat.invert_in_place()
            base.mouseInterfaceNode.set_mat(mat)
            base.enableMouse()
            self.debug_cam = True
        else:
            base.disableMouse()
            self.reset_camera()
            self.debug_cam = False

    def toggle_range(self, ability):
        rangeindicator = self.player_ranges[ability]
        rangeindicator.visible = not rangeindicator.visible

    def reset_camera(self):
        campos = self.player.get_pos()
        campos.z += 25
        campos.y -= 25
        base.camera.set_mat(p3d.LMatrix4.ident_mat())
        base.cam.set_pos(campos)
        base.cam.look_at(self.player.get_pos())

    def update(self, dt):
        # Update player position
        movvec = self.target - self.player.get_pos()
        newpos = self.player.get_pos()
        if movvec.length_squared() < 0.4:
            newpos.set_x(self.target.x)
            newpos.set_y(self.target.y)
        else:
            movvec.normalize()
            movvec *= self.PLAYER_SPEED * dt
            newpos.set_x(self.player.get_x() + movvec.x)
            newpos.set_y(self.player.get_y() + movvec.y)
            self.player.look_at(newpos)

        if self.dungeon.is_walkable(*newpos.xy):
            self.player.set_pos(newpos)

        if self.dungeon.is_exit(*newpos.xy):
            next_didx = self.dungeon_idx + 1
            if next_didx >= len(self.dungeons):
                # Create a new dungeon
                self.dungeons.append(Dungeon(self.mapgen, self.DUNGEON_SX, self.DUNGEON_SY))
            self.switch_to_dungeon(next_didx)

        if self.last_tele_loc is not None:
            if (self.last_tele_loc - self.player.get_pos()).length_squared() > 3:
                self.last_tele_loc = None

        if self.last_tele_loc is None:
            teleloc = self.dungeon.get_tele_loc(*newpos.xy)
            if teleloc is not None:
                newpos.x, newpos.y = teleloc
                self.last_tele_loc = p3d.LVector3(newpos)
                self.target = p3d.LVector3(newpos)
                self.player.set_pos(newpos)
                self.reset_camera()

        if not self.debug_cam and base.mouseWatcherNode.has_mouse():
            mousex, mousey = base.mouseWatcherNode.get_mouse()
            border = self.CAM_MOVE_BORDER
            camdelta = p3d.LVector2(0, 0)
            if mousex < -border:
                camdelta.x -= 1
            elif mousex > border:
                camdelta.x += 1

            if mousey < -border:
                camdelta.y -= 1
            elif mousey > border:
                camdelta.y += 1

            camdelta.normalize()
            camdelta *= self.CAM_MOVE_SPEED * dt

            campos = base.cam.get_pos()
            campos.x += camdelta.x
            campos.y += camdelta.y
            base.cam.set_pos(campos)

    def move_player(self):
        if not base.mouseWatcherNode.has_mouse() or self.debug_cam:
            return

        mousevec = base.mouseWatcherNode.get_mouse()
        near = p3d.LPoint3()
        far = p3d.LPoint3()
        base.camLens.extrude(mousevec, near, far)
        near = self.root_node.get_relative_point(base.cam, near)
        far = self.root_node.get_relative_point(base.cam, far)

        plane = p3d.Plane(p3d.LVector3.up(), p3d.LPoint3())
        worldpos = p3d.LPoint3()
        if plane.intersects_line(worldpos, near, far):
            self.target.set_x(worldpos.x)
            self.target.set_y(worldpos.y)

    def switch_to_dungeon(self, dungeon_idx):
        # Switch to next layer
        print("Switching to dungeon layer {}".format(dungeon_idx))
        self.dungeon.model_root.detach_node()
        self.dungeon = self.dungeons[dungeon_idx]
        self.dungeon.model_root.reparent_to(self.root_node)
        self.dungeon_idx = dungeon_idx
        self.player.set_pos(self.dungeon.player_start)
        self.player.set_z(1.5)
        self.target = self.player.get_pos()
        self.reset_camera()


from apps.users.models import EmailUser
from django.forms import ModelForm

from .models import Hit

class HitCreateForm(ModelForm):
    class Meta:
        model = Hit
        fields = ('target', 'description',  'hitmen_by')

    def __init__(self, *args, manager=None, **kwargs):
        super().__init__(*args, **kwargs)
        if manager:
            self.fields['hitmen_by'].choices = [
               (x.id, x) for x in EmailUser.objects.filter(managed_by=manager)]

class HitUpdateForm(ModelForm):
    class Meta:
        model = Hit
        fields = ('status',)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['status'].choices = [
            ('failed', 'Failed'),
            ('completed', 'Completed')]


import argparse
import os
import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_error, r2_score
from joblib import dump, load

# inference functions ---------------
def model_fn(model_dir):
    clf = load(os.path.join(model_dir, "model.joblib"))
    return clf

if __name__ == "__main__":
    print('Training started')
    parser = argparse.ArgumentParser()
    parser.add_argument('--train', type=str, default=os.environ.get('SM_CHANNEL_TRAIN'))
    parser.add_argument('--model-dir', type=str, default=os.environ.get('SM_MODEL_DIR'))
    parser.add_argument('--output-data-dir', type=str, default=os.environ.get('SM_OUTPUT_DATA_DIR'))

    parser.add_argument('--n_estimators', type=int, default=100)
    parser.add_argument('--max_depth', type=int, default=15)
    parser.add_argument('--max_features', type=str, default='auto')
    parser.add_argument('--min_samples_split', type=int, default=10)
    parser.add_argument('--min_samples_leaf', type=int, default=20)
    parser.add_argument('--currency', type=str)
    args, _ = parser.parse_known_args()

    print('Args', args)
    currency = args.currency
    input_data = os.path.join(args.train, f'USD_{currency}_features.csv')
    df = pd.read_csv(input_data, parse_dates=[0], index_col=0)

    df['1d_future'] = df['1d_delta'].shift(-1)
    df.dropna(inplace=True)

    ## Prepare data for model training

    scaler = StandardScaler();
    data = scaler.fit_transform(df)
    data = pd.DataFrame(data, index=df.index, columns = df.columns)
    features = np.array(data.columns).tolist()
    features.remove('1d_future')
    x = data[features]
    y = data['1d_future']
    train_size = int(0.7 * y.shape[0])
    x_train = x[:train_size]
    y_train = y[:train_size]
    x_test = x[train_size:]
    y_test = y[train_size:]

    rf_model = RandomForestRegressor(
        n_estimators=args.n_estimators,
        max_depth=args.max_depth,
        max_features=args.max_features,
        min_samples_split=args.min_samples_split,
        min_samples_leaf=args.min_samples_leaf,
        random_state=42
    )

    rf_model.fit(x_train, y_train)
    y_pred = rf_model.predict(x_test)
    mse = mean_squared_error(y_test, y_pred)
    r2 = r2_score(y_test, y_pred)

    print(f'Mean Squared Error: {mse:.6f}')
    print(f'R2 Score: {r2:.6f}')
    dump(rf_model, os.path.join(args.model_dir, 'model.joblib'))


# training / eval for burger equation

"""
This is the main file to train the GNN on the burger equation

The burger equation is a simple PDE that can be solved with a GNN (and a pinn loss).
The PDE is :
    u_t + u*u_x = mu * u_xx

With the following boundary conditions:
    u(-1, t) = 0
    u(1, t) = 0
    u(x, 0) = sin(pi * x)

The goal is to train a GNN to solve this PDE.
"""

import sys
sys.path.append("/app/")

from discretize_pinn_loss.pdebenchmark_darcy import Darcy2DPDEDataset
from discretize_pinn_loss.models_fno import FNO2d
from discretize_pinn_loss.losses_darcy import DarcyFlowOperator, DarcyLoss

import pytorch_lightning as pl

import torch
from torch_geometric.data import Data, DataLoader, Batch

import hashlib
import datetime
import math

import os

import wandb

import matplotlib.pyplot as plt

import einops

import numpy as np

torch.manual_seed(42)

class FnoFull(pl.LightningModule):
    def __init__(self, model_fno, loss_function):
        super().__init__()

        self.model = model_fno
        self.loss_function = loss_function

        self.loss_mse = torch.nn.MSELoss()

    def forward(self, graph):

        # get the input node and reshape it because the model expects a batch
        nodes = graph.x

        batch_size = graph.ptr.shape[0] - 1

        size_image = math.sqrt(int(nodes.shape[0] / batch_size))

        nodes = einops.rearrange(nodes, "(b n) d -> b n d", b=batch_size)

        images = einops.rearrange(nodes, "b (s sb) d -> b s sb d", s=int(size_image), sb=int(size_image))

        result = self.model(images)

        result = einops.rearrange(result, "b s sb n -> b (s sb) n", s=int(size_image), sb=int(size_image))

        result = einops.rearrange(result, "b n d -> (b n) d")

        return result

    def training_step(self, batch, batch_idx):

        graph_a_x = batch
        edge_index = graph_a_x.edge_index
        edges = graph_a_x.edge_attr

        # forward pass
        nodes_pred = self.forward(graph_a_x)

        # we create the two graphs
        graph_pred = Data(x=nodes_pred, edge_index=edge_index, edge_attr=edges)

        # compute loss
        relative_loss = self.loss_function(graph_pred, graph_a_x)

        loss = self.loss_mse(relative_loss, torch.zeros_like(relative_loss))

        self.log("train_loss", loss)

        return loss

    def validation_step(self, batch, batch_idx):

        a_x = batch

        mask = batch.mask

        # forward pass
        u_x = self.forward(a_x)

        # we create the two graphs
        graph_pred = Data(x=u_x, edge_index=a_x.edge_index, edge_attr=a_x.edge_attr)

        print("graph_pred", graph_pred)
        print("a_x", a_x)

        # compute loss
        relative_loss = torch.abs(self.loss_function(graph_pred, a_x, mask))

        print("relative_loss", relative_loss.shape)

        loss = self.loss_mse(relative_loss, torch.zeros_like(relative_loss))

        self.log("val_loss", loss)

        # now we can save the result image to wandb for visualization
        # we need to reshape the image to be able to plot it
        batch_size = a_x.ptr.shape[0] - 1

        # we should also log the loss with the generated target value coming form the solver (data)
        graph_target= Data(x=batch.target, edge_index=batch.edge_index, edge_attr=batch.edge_attr)

        relative_loss_solver = torch.abs(self.loss_function(graph_target, batch, mask=mask))

        loss_solver = self.loss_mse(relative_loss_solver, torch.zeros_like(relative_loss_solver))

        self.log("val_loss_solver", loss_solver)

        size_image = math.sqrt(int(a_x.x.shape[0] / batch_size))

        a_x = einops.rearrange(a_x.x, "(b n) d -> b n d", b=batch_size)
        a_x = einops.rearrange(a_x, "b (s sb) d -> b s sb d", s=int(size_image), sb=int(size_image))
        u_x = einops.rearrange(u_x, "(b n) d -> b n d", b=batch_size)
        u_x = einops.rearrange(u_x, "b (s sb) d -> b s sb d", s=int(size_image), sb=int(size_image))

        # same operation for the target
        target = batch.target

        target = einops.rearrange(target, "(b n) d -> b n d", b=batch_size)
        target = einops.rearrange(target, "b (s sb) d -> b s sb d", s=int(size_image), sb=int(size_image))

        #same thing on relative loss
        relative_loss = einops.rearrange(relative_loss.squeeze(), "(b n) -> b n", b=batch_size)
        relative_loss = einops.rearrange(relative_loss, "b (s sb) -> b s sb", s=int(size_image), sb=int(size_image))

        #same thing on relative loss solver
        relative_loss_solver = einops.rearrange(relative_loss_solver.squeeze(), "(b n) -> b n", b=batch_size)
        relative_loss_solver = einops.rearrange(relative_loss_solver, "b (s sb) -> b s sb", s=int(size_image), sb=int(size_image))

        # now we can save the result image to wandb for visualization
        # we need to reshape the image to be able to plot it
        # save the image into png format
        fig, axs = plt.subplots(1, 5, figsize=(15, 5))

        pos0 = axs[0].imshow(a_x[0, :, :, 0].detach().cpu().numpy())
        axs[0].set_title("a_x")

        pos1 = axs[1].imshow(u_x[0, :, :, 0].detach().cpu().numpy(), vmin=0, vmax=0.30)
        axs[1].set_title("u_x")

        pos2 = axs[2].imshow(target[0, :, :, 0].detach().cpu().numpy(), vmin=0, vmax=0.30)
        axs[2].set_title("target")

        pos3 = axs[3].imshow(relative_loss[0, :, :].detach().cpu().numpy(), vmin=0, vmax=4)
        axs[3].set_title("relative loss pred")

        pos4 = axs[4].imshow(relative_loss_solver[0, :, :].detach().cpu().numpy(), vmin=0, vmax=4)
        axs[4].set_title("relative loss solver")

        fig.colorbar(pos0, ax=axs[0])
        fig.colorbar(pos1, ax=axs[1])
        fig.colorbar(pos2, ax=axs[2])
        fig.colorbar(pos3, ax=axs[3])
        fig.colorbar(pos4, ax=axs[4])

        # save into png
        fig.savefig("tmp.png")

        self.logger.log_image("a_x u_x target", ["tmp.png"])

        return loss

    # on validation epoch end
    def validation_epoch_end(self, outputs):
        pass

    def configure_optimizers(self):
        optimizer = torch.optim.Adam(self.model.parameters(), lr=1e-3)
        return optimizer

    def test_train_step(self, dataloader):

        # init optimizer
        optimizer = torch.optim.Adam(self.model.parameters(), lr=1e-5)

        with torch.autograd.detect_anomaly():

            for batch in dataloader:
                loss = self.training_step(batch, 0)
                print("loss", loss)

                loss.backward()
                optimizer.step()
                optimizer.zero_grad()

def train():

    # create domain
    nb_space = 128

    delta_x = 1.0 / nb_space
    delta_y = delta_x

    batch_size = 1

    # init dataset and dataloader
    path = "/app/data/2D_DarcyFlow_beta1.0_Train.hdf5"
    dataset = Darcy2DPDEDataset(path_hdf5=path, delta_x=delta_x, delta_y=delta_y)

    # divide into train and test
    train_size = int(0.95 * len(dataset))
    test_size = len(dataset) - train_size

    train_dataset, test_dataset = torch.utils.data.random_split(dataset, [train_size, test_size], generator=torch.Generator().manual_seed(42))

    dataloader_train = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=0)
    dataloader_test = DataLoader(test_dataset, batch_size=batch_size, shuffle=False, num_workers=0)

    # init model
    input_dim = 3
    modes = 32
    width = 32

    path_init = "/app/discretize_pinn_loss/data_init/init_solution.pt"

    # we create the model
    model = FNO2d(modes1=modes, modes2=modes,  width=width, input_dim=input_dim, path_init=path_init)

    # we create the burger function
    darcy_loss = DarcyLoss(index_derivative_node=0, index_derivative_x=0, index_derivative_y=1, delta_y=delta_y, delta_x=delta_x)

    # we create the trainer
    gnn_full = FnoFull(model, darcy_loss)

    # we create the trainer with the logger
    # init wandb key
    # read wandb_key/key.txt file to get the key
    with open("/app/wandb_key/key.txt", "r") as f:
        key = f.read()

    os.environ['WANDB_API_KEY'] = key
    wandb.init(project='2D_Darcy', entity='forbu14')

    wandb_logger = pl.loggers.WandbLogger(project="2D_Darcy", name="2D_Darcy")
    trainer = pl.Trainer(max_epochs=10, logger=wandb_logger, gradient_clip_val=0.5, accumulate_grad_batches=2)

    # test with test_train_step
    # gnn_full.test_train_step(dataloader_train)

    # we train
    trainer.fit(gnn_full, dataloader_train, dataloader_test)

    # we save the model
    torch.save(trainer.model.state_dict(), "models_params/darcy_model.pt")

    return gnn_full

if __name__ == "__main__":
    train()


import random
import matplotlib.pyplot as plt
import numpy as np
import math
import copy
from typing import List
from matplotlib.patches import Arc
from plotly_util import PlotlyUtil

tolerancia = 10 ** -13
lista_colores = ["r", "b", "g", "c", "m", "y", "k"]
lista_espesores = [x / 5 for x in range(10, 20)]

class Nodo(object):
    def __init__(self, x: (int, float), y: (int, float)):
        self.x = x
        self.y = y

    def __eq__(self, otro_nodo):  # Determinar si dos puntos son iguales, bajo el valor de tolerancia
        return self.x - tolerancia <= otro_nodo.x <= self.x + tolerancia and self.y - tolerancia <= otro_nodo.y <= self.y + tolerancia

class ListaDeNodos(object):
    """Objeto que alberga una lista de elementos de clase Nodo."""

    def __init__(self, lista_nodos):
        self.lista_nodos = lista_nodos

    def eliminar_duplicados(self):
        resultado = [self.lista_nodos[0]]
        for nodo in self.lista_nodos[1:]:
            if not any(nodo == nodo_b for nodo_b in resultado):
                resultado.append(nodo)
        return resultado

class Segmento(object):
    """Segmento en dos dimensiones definido a partir de 2 Nodos de paso."""

    def __init__(self, nodo_1: Nodo, nodo_2: Nodo):
        self.nodo_1 = nodo_1
        self.nodo_2 = nodo_2
        self.recta_segmento = Recta(nodo_1, nodo_2)
        self.extremos_x, self.extremos_y = self.extremos_segmento()

    def obtener_parametros_ecuacion_recta(self):
        x1, y1 = self.nodo_1.x, self.nodo_1.y
        x2, y2 = self.nodo_2.x, self.nodo_2.y
        a = y1 - y2
        b = x2 - x1
        c = y2 * (x1 - x2) + (y2 - y1) * x2
        return a, b, c

    def determinar_si_nodo_esta_en_rango(self, nodo):
        xmin, xmax = self.extremos_x
        ymin, ymax = self.extremos_y
        return xmin - tolerancia <= nodo.x <= xmax + tolerancia and ymin - tolerancia <= nodo.y <= ymax + tolerancia

    def determinar_si_nodo_pertenece_a_segmento(self, nodo):
        return self.recta_segmento.ecuacion_recta(nodo) == 0 and self.determinar_si_nodo_esta_en_rango(nodo)

    def extremos_segmento(self):
        extramos_x = (min(self.nodo_1.x, self.nodo_2.x), max(self.nodo_1.x, self.nodo_2.x))
        extramos_y = (min(self.nodo_1.y, self.nodo_2.y), max(self.nodo_1.y, self.nodo_2.y))
        return extramos_x, extramos_y

    def obtener_interseccion_recta(self, recta):
        nodo_interseccion_rectas = recta & self.recta_segmento
        if not nodo_interseccion_rectas:  # Paralelas
            return None
        resultado = nodo_interseccion_rectas if self.determinar_si_nodo_esta_en_rango(
            nodo_interseccion_rectas) else None
        return resultado

    def plot(self, ax=None, **kwargs):
        if ax is None:
            plt.plot([self.nodo_1.x, self.nodo_2.x], [self.nodo_1.y, self.nodo_2.y], **kwargs)
        else:
            ax.plot([self.nodo_1.x, self.nodo_2.x], [self.nodo_1.y, self.nodo_2.y], **kwargs)

    def __and__(self, otro_segmento):
        result = self.recta_segmento & otro_segmento.recta_segmento  # Buscando interseccion
        if not result:
            return None
        return result if self.determinar_si_nodo_esta_en_rango(
            result) and otro_segmento.determinar_si_nodo_esta_en_rango(result) else None

class Recta(object):
    def __init__(self, nodo_1: Nodo, nodo_2: Nodo):
        self.nodo_1 = nodo_1
        self.nodo_2 = nodo_2
        a, b, c = self.obtener_parametros_ecuacion_implicita(nodo_1, nodo_2)
        self.ecuacion_recta = lambda nodo: a * nodo.x + b * nodo.y + c

        # Definición de métodos a ser usados.
        self.distancia_a_nodo = lambda nodo: (a * nodo.x + b * nodo.y + c) / ((a ** 2 + b ** 2) ** 0.5)
        self.distancia_a_nodo_v = lambda nodo: abs(nodo.y + (c + a * nodo.x) / b) if b != 0 else None
        self.distancia_a_nodo_h = lambda nodo: abs(nodo.x + (c + b * nodo.y) / a) if a != 0 else None

        # Distancia en Vertical y Horizontal, respectivamente.
        self.y = lambda x: -c / b - a / b * x
        self.x = lambda y: -c / a - b / a * y

    @staticmethod
    def obtener_parametros_ecuacion_implicita(nodo_1, nodo_2):
        """Obtiene los parámetros a, b y c de una recta, de manera de describir
        la ecuación de la misma en el plano de la forma a*x+b*y*c (tipo cartesiana o implícita)"""
        x1, y1 = nodo_1.x, nodo_1.y
        x2, y2 = nodo_2.x, nodo_2.y
        a = y1 - y2
        b = x2 - x1
        c = y2 * (x1 - x2) + (y2 - y1) * x2
        return a, b, c

    def mostrar_recta(self):
        plt.plot([self.nodo_1.x, self.nodo_2.x], [self.nodo_1.y, self.nodo_2.y])

    def __and__(self, otra_recta) -> Nodo or None:
        """Realiza la intersección de la recta con otra provista, en el plano.
        :param otra_recta: recta a intersectar.
        :return Nodo de intersección or None si no se encuentran"""
        x1, y1, x2, y2 = self.nodo_1.x, self.nodo_1.y, self.nodo_2.x, self.nodo_2.y
        x3, y3, x4, y4 = otra_recta.nodo_1.x, otra_recta.nodo_1.y, otra_recta.nodo_2.x, otra_recta.nodo_2.y
        det = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)  # Determinante de la matriz de coordenadas
        if -1 * tolerancia <= det <= tolerancia:  # Cuando den tiende a 0, las rectas son paralelas, no hay intersección.
            return None
        # Se aplica la fórmula de Cramer para resolver el sistema de ecuaciones lineal.
        x = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / det
        y = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / det
        return Nodo(x, y)

class Poligono(object):
    """Polígono CONVEXO en el plano, formado por una serie de nodos."""

    def __init__(self, nodos: List[Nodo], ordenar: bool = False):
        """
        :param nodos: lista de nodos que definen el contorno del polígono.
        :param ordenar: define si la lista de nodos anterior debe ser ordenada en sentido antihorario, siendo dicho
         sentido el convencional para el programa."""

        self.nodos_extremos = nodos if not ordenar else self.ordenar_nodos_poligono_convexo_antihorario(nodos)
        self.x = [nodo.x for nodo in self.nodos_extremos]
        self.y = [nodo.y for nodo in self.nodos_extremos]
        self.total_de_nodos = len(nodos)
        self.segmentos_borde = self.obtener_segmentos_borde()
        valor_area = self.determinar_area_poligono()
        self.area = valor_area
        self.nodo_centroide = self.determinar_centroide()
        self.xg, self.yg = self.nodo_centroide.x, self.nodo_centroide.y
        # Se definen estos atributos para determinar si el elemento fue modificado por una intersección (ver método)
        self.nodos_interseccion_lista = []
        self.numero_de_modificaciones = 0
        self.nodo_centroide_original = copy.copy(self.nodo_centroide)
        self.area_original = valor_area

    @staticmethod
    def ordenar_nodos_poligono_convexo_antihorario(nodos=None):
        """Ordena los nodos en sentido antihorario, cuando el polígono SEA CONVEXO."""
        if nodos is None:
            return None
        x_array = np.array([nodo.x for nodo in nodos])
        y_array = np.array([nodo.y for nodo in nodos])

        # Coordenadas del centroide
        x0 = np.mean(x_array)
        y0 = np.mean(y_array)

        # Calcular las distancias desde el centroide a cada nodo
        r = np.sqrt((x_array - x0) ** 2 + (y_array - y0) ** 2)
        # Calcula el ángulo antihorario con respecto al eje x de cada punto, y ordena en base al mismo.
        angulos = np.where((y_array - y0) > 0, np.arccos((x_array - x0) / r), 2 * np.pi - np.arccos((x_array - x0) / r))
        mask = np.argsort(angulos)
        x_sorted = x_array[mask]
        y_sorted = y_array[mask]

        nodos_ordenados = []
        for i in range(len(x_sorted)):
            nodos_ordenados.append(Nodo(x_sorted[i], y_sorted[i]))
        return nodos_ordenados

    def obtener_segmentos_borde(self):
        """A partir de los nodos que fueron ingresados, obtiene la lista de segmentos de borde."""
        i = 0
        imax = len(self.nodos_extremos)
        lista_de_segmentos = []
        while i < imax:
            nodo_1 = self.nodos_extremos[i]
            nodo_2 = self.nodos_extremos[i + 1] if i + 1 < imax else self.nodos_extremos[0]
            lista_de_segmentos.append(Segmento(nodo_1, nodo_2))
            i = i + 1
        return lista_de_segmentos

    def determinar_area_poligono(self):
        i = 0
        imax = len(self.nodos_extremos)
        area = 0
        while i < imax:
            nodo_1 = self.nodos_extremos[i]
            nodo_2 = self.nodos_extremos[i + 1] if i + 1 < imax else self.nodos_extremos[0]
            area = area + (nodo_1.x * nodo_2.y - nodo_2.x * nodo_1.y)
            i = i + 1
        return abs(area / 2)

    def determinar_centroide(self):
        try:
            i = 0
            imax = len(self.nodos_extremos)
            cx, cy = 0, 0
            while i < imax:
                nodo_1 = self.nodos_extremos[i]
                nodo_2 = self.nodos_extremos[i + 1] if i + 1 < imax else self.nodos_extremos[0]
                cx = cx + (nodo_1.x + nodo_2.x) * (nodo_1.x * nodo_2.y - nodo_2.x * nodo_1.y)
                cy = cy + (nodo_1.y + nodo_2.y) * (nodo_1.x * nodo_2.y - nodo_2.x * nodo_1.y)
                i = i + 1
            return Nodo(cx / (6 * self.area), cy / (6 * self.area))
        except RuntimeWarning as e:
            print(e)

    def determinar_si_nodo_pertence_a_contorno(self, nodo_a_buscar: Nodo):
        """Devuelve verdadero si el nodo se encuentra dentro del contorno, sin incluir los bordes del mismo."""
        for i, nodo in enumerate(self.nodos_extremos):
            nodo_1, nodo_2, nodo_3 = self.obtener_3_nodos_x_indice(i)
            recta = Recta(nodo_1, nodo_2)  # Recta definida por el nodo 1 y nodo 2.
            # La ecuación de la recta devolverá valores del mismo signo para puntos del mismo semiplano.
            signo_semiplano_contorno = recta.ecuacion_recta(nodo_3)
            if signo_semiplano_contorno == 0:
                raise Exception(f"Error de Ingreso de datos: Ingresar puntos en el contorno que no estén alineados", )
            signo_semiplano_elemento = recta.ecuacion_recta(nodo_a_buscar)
            if signo_semiplano_contorno * signo_semiplano_elemento < 0:  # Si son de distinto signo, no pertenece.
                return False
        return True

    def determinar_si_nodo_pertence_a_contorno_sin_borde(self, nodo_a_buscar: Nodo):
        """Devuelve verdadero si el nodo se encuentra dentro del contorno, incluyendo los bordes del mismo."""
        for i, nodo in enumerate(self.nodos_extremos):
            nodo_1, nodo_2, nodo_3 = self.obtener_3_nodos_x_indice(i)
            recta = Recta(nodo_1, nodo_2)  # Recta definida por el nodo 1 y nodo 2.
            # La ecuación de la recta devolverá valores del mismo signo para puntos del mismo semiplano.
            signo_semiplano_contorno = recta.ecuacion_recta(nodo_3)
            if signo_semiplano_contorno == 0:
                raise Exception("Error de Ingreso de datos: Ingresar puntos en el contorno que no estén alineados.")
            signo_semiplano_elemento = recta.ecuacion_recta(nodo_a_buscar)
            if signo_semiplano_contorno * signo_semiplano_elemento <= 0:  # Si son de distinto signo, no pertenece.
                return False
        return True

    def obtener_3_nodos_x_indice(self, i_punto_1):
        """
        Obtiene el nodo en cuestión y los 2 nodos subsiguientes, basado en el índice del primer nodo.
        Al quedar algún índice fuera de rango, se obtiene el primer o segundo elemento según corresponda."""
        i_punto_2 = self.obtener_indice(i_punto_1, 1)
        i_punto_3 = self.obtener_indice(i_punto_1, 2)
        return self.nodos_extremos[i_punto_1], self.nodos_extremos[i_punto_2], self.nodos_extremos[i_punto_3]

    def obtener_indice(self, indice_punto_1, valor_a_sumar: int):
        """Obtiene el índice del próximo elemento, dependiendo de valor_a_sumar"""
        i_punto = indice_punto_1 + valor_a_sumar
        return i_punto if i_punto < self.total_de_nodos else i_punto - self.total_de_nodos

    def plot(self, indice_color=None,
             espesor=None,
             mostrar_centroide=False,
             texto_a_mostrar=None,
             tridimensional=False,
             ec_plano_3d=None,
             plt_3d=None,
             transparencia=1,
             ax=None):

        color = random.choice(lista_colores) if indice_color is None else lista_colores[indice_color]
        espesor = random.choice(lista_espesores) if espesor is None else espesor
        x = []
        y = []
        for segmento in self.segmentos_borde:
            x.extend([segmento.nodo_1.x, segmento.nodo_2.x])
            y.extend([segmento.nodo_1.y, segmento.nodo_2.y])

        if not tridimensional:
            plt.plot(x, y, c=color, alpha=transparencia, linewidth=espesor, zorder=0)
        else:  # Figura 3d
            z1 = [0] * len(x)
            z2 = [ec_plano_3d(x[i], y[i]) for i in range(len(x))]
            x = x * 2
            y = y * 2
            z = z1 + z2
            for i in range(len(x) - 1):
                x0, y0, z0 = [x[i], x[i + 1]], [y[i], y[i + 1]], [z[i], z[i + 1]]
                plt_3d.plot(z0, x0, y0,
                            c=color if i != len(x) / 2 - 1 else "w",
                            alpha=transparencia, linewidth=espesor, zorder=0)

        if mostrar_centroide:
            plt.scatter(self.xg, self.yg, c=color, marker=".", zorder=0)
        if texto_a_mostrar:
            plt.text(self.xg, self.yg, texto_a_mostrar)

    def obtener_poligono_interseccion(self, otro_poligono):
        """Obtiene el poligono que resulta de intersectar self con otro_poligono"""
        lista_nodos_interseccion = self & otro_poligono
        if not lista_nodos_interseccion:
            return self
        # Determinar que nodos de otro_poligono pertenecen a self
        for nodo in otro_poligono.nodos_extremos:
            if self.determinar_si_nodo_pertence_a_contorno_sin_borde(nodo):
                lista_nodos_interseccion.append(nodo)
        for nodo_pos in self.nodos_extremos:  # Determinar que nodos de self pertenecen a otro_poligono
            if otro_poligono.determinar_si_nodo_pertence_a_contorno_sin_borde(nodo_pos):
                lista_nodos_interseccion.append(nodo_pos)
        if len(lista_nodos_interseccion) <= 2:
            return self
        lista_nodos_interseccion = ListaDeNodos(lista_nodos_interseccion).eliminar_duplicados()
        return Poligono(lista_nodos_interseccion, ordenar=True)

    def restar_con_otro_poligono(self, otro_poligono):
        """Resta al elemento self el complemento con otro_poligono.
        :param otro_poligono: polígono que se le restará a self.
        :return el mismo elemento self, pero modificado por la resta."""

        lista_de_nodos_de_interseccion = self.obtener_nodos_compartidos_entre_poligonos(self, otro_poligono)

        if len(lista_de_nodos_de_interseccion) <= 2:
            return self

        poligono_interno = Poligono(lista_de_nodos_de_interseccion, ordenar=True)

        if self.area_y_centro_son_iguales(poligono_1=self, poligono_2=poligono_interno):
            # Si la interseccion es igual al mismo poligono, será eliminado.
            return None

        nueva_area = self.area - poligono_interno.area
        nueva_x = (self.area * self.xg - poligono_interno.area * poligono_interno.xg) / nueva_area
        nueva_y = (self.area * self.yg - poligono_interno.area * poligono_interno.yg) / nueva_area

        if not self.nuevo_poligono_es_valido(nueva_area, nueva_x, nueva_y):
            return None
        self.numero_de_modificaciones = self.numero_de_modificaciones + 1

        self.area = nueva_area
        self.xg, self.yg = nueva_x, nueva_y
        self.nodo_centroide = Nodo(nueva_x, nueva_y)
        self.nodos_interseccion_lista = lista_de_nodos_de_interseccion + self.nodos_interseccion_lista
        return self  # self modificado

    @staticmethod
    def nuevo_poligono_es_valido(nueva_area, nueva_x, nueva_y):  # Criterio: área=0|nodo en infinito numérico.
        return not (-tolerancia <= nueva_area <= tolerancia or nueva_x == float("inf") or nueva_y == float("inf"))

    @staticmethod
    def obtener_nodos_compartidos_entre_poligonos(poligono_1, poligono_2):
        """Devuelve una lista de:
        Aquellos nodos que intersectan alguno de los bordes entre los polígonos
        Los nodos de poligono_1 que se encuentran dentro de poligono_2, y viceversa."""

        lista_nodos_interseccion = poligono_1 & poligono_2  # Intersección de bordes
        if not lista_nodos_interseccion:  # no hay intersección
            return []

        for nodo in poligono_2.nodos_extremos:  # Determinar qué nodos de otro_poligono pertenecen a self
            if poligono_1.determinar_si_nodo_pertence_a_contorno_sin_borde(nodo):
                lista_nodos_interseccion.append(nodo)
        for nodo_pos in poligono_1.nodos_extremos:  # Determinar qué nodos de self pertenecen a otro_poligono
            if poligono_2.determinar_si_nodo_pertence_a_contorno_sin_borde(nodo_pos):
                lista_nodos_interseccion.append(nodo_pos)
        nodos_interseccion = ListaDeNodos(lista_nodos_interseccion).eliminar_duplicados()
        return nodos_interseccion

    def __and__(self, otro_poligono):
        """Devuelve los nodos de interseccion de dos poligonos"""
        lista_nodos_interseccion = []
        for segmento_contorno in self.segmentos_borde:
            for segmento_rectangulo in otro_poligono.segmentos_borde:
                interseccion = segmento_contorno & segmento_rectangulo
                if interseccion:
                    nodo_interseccion = Nodo(interseccion.x, interseccion.y)
                    if not (any(nodo_interseccion == nodo_x for nodo_x in lista_nodos_interseccion)):
                        lista_nodos_interseccion.append(nodo_interseccion)
        return lista_nodos_interseccion or None

    def __lt__(self, poligono_mayor):
        """Determina si self esta contenido dentro de poligono mayor"""
        centro_pertenece_a_contorno = poligono_mayor.determinar_si_nodo_pertence_a_contorno(self.nodo_centroide)
        if not centro_pertenece_a_contorno:
            return False
        intersecciones = poligono_mayor & self
        return True if not intersecciones else False

    @staticmethod
    def area_y_centro_son_iguales(poligono_1, poligono_2):
        """Valida si dos poligonos son iguales"""
        return poligono_2.area - tolerancia <= poligono_1.area <= poligono_2.area + tolerancia and \
            poligono_2.xg - tolerancia <= poligono_1.xg <= poligono_2.xg + tolerancia and \
            poligono_2.yg - tolerancia <= poligono_1.yg <= poligono_2.yg + tolerancia

    def desplazar_sistema_de_referencia(self, desp_x, desp_y):
        self.xg = self.xg + desp_x
        self.yg = self.yg + desp_y
        # Para que se muestre la discretización correctamente
        for nodo_extremo in self.nodos_extremos:
            nodo_extremo.x = nodo_extremo.x + desp_x
            nodo_extremo.y = nodo_extremo.y + desp_y
        self.segmentos_borde = self.obtener_segmentos_borde()

class ElementoRectangular(Poligono):
    """El elemento finito que compone a un Polígono mayor.
     Al estar formado por 4 nodos compone en sí un Polígono."""

    def __init__(self, ubicacion_centro: Nodo, medidas: tuple):
        a, b = medidas
        self.lado_x, self.lado_y = a, b
        self.x_centro = ubicacion_centro.x
        self.y_centro = ubicacion_centro.y
        self.ubicacion_centro = ubicacion_centro
        self.medidas = medidas
        self.perimetro = 2 * a + 2 * b
        super().__init__(self.obtener_nodos_extremos())

    def determinar_area_poligono(self):
        return self.lado_x * self.lado_y

    def determinar_centroide(self):
        return self.ubicacion_centro

    def obtener_nodos_extremos(self):
        x, y = self.x_centro, self.y_centro
        a, b = self.lado_x, self.lado_y
        return Nodo(x + a / 2, y + b / 2), Nodo(x + a / 2, y - b / 2), Nodo(x - a / 2, y - b / 2), Nodo(x - a / 2,
                                                                                y + b / 2)

    def obtener_segmentos_borde(self):
        return (
            Segmento(self.nodos_extremos[0], self.nodos_extremos[1]),
            Segmento(self.nodos_extremos[1], self.nodos_extremos[2]),
            Segmento(self.nodos_extremos[2], self.nodos_extremos[3]),
            Segmento(self.nodos_extremos[3], self.nodos_extremos[0])
        )

class ElementoTrapecioCircular():
    def __init__(self, nodo_centro: Nodo, radios: tuple, angulos):
        self.area = math.radians(angulos[1] - angulos[0]) * (radios[1] ** 2 - radios[0] ** 2) / 2
        angulo_medio = math.radians(angulos[1] + angulos[0]) / 2

        self.angulo_inicial, self.angulo_final = min(angulos), max(angulos)
        self.radio_interno, self.radio_externo = min(radios), max(radios)
        self.nodo_centro = nodo_centro
        self.xc = nodo_centro.x
        self.yc = nodo_centro.y

        theta = math.radians(self.angulo_final - self.angulo_inicial) / 2

        # El centroide de un sector circular se define como 2R sin(θ)/(3θ); siendo R el radio y θ la apertura del sector
        area_1 = math.radians(self.angulo_final - self.angulo_inicial) * (self.radio_externo ** 2) / 2
        area_2 = math.radians(self.angulo_final - self.angulo_inicial) * (self.radio_interno ** 2) / 2
        radio_al_centroide = 2 * math.sin(theta) / (3 * theta) * (
                    self.radio_externo * area_1 - self.radio_interno * area_2) / self.area
        self.xg = math.cos(angulo_medio) * radio_al_centroide + self.xc
        self.yg = math.sin(angulo_medio) * radio_al_centroide + self.yc

        self.nodos_extremos = self.obtener_nodos_extremos()
        self.segmentos_rectos = self.obtener_segmentos_rectos()

    def obtener_nodos_extremos(self):
        resultado = []
        if self.radio_interno == 0:  # Circulo
            return []
        for theta in [self.angulo_inicial, self.angulo_final]:
            for r in [self.radio_interno, self.radio_externo]:
                resultado.append(
                    Nodo(self.xc + r * math.cos(math.radians(theta)), self.yc + r * math.sin(math.radians(theta))))
        return resultado

    def obtener_segmentos_rectos(self):
        if len(self.nodos_extremos) == 0:
            return None
        return (
            Segmento(self.nodos_extremos[0], self.nodos_extremos[1]),
            Segmento(self.nodos_extremos[2], self.nodos_extremos[3])
        )

    def nodo_en_elemento(self, nodo: Nodo):
        xc, yc = nodo.x - self.nodo_centro.x, nodo.y - self.nodo_centro.y
        angulo = math.atan2(yc, xc)
        angulo = angulo + 360 if angulo < 0 else angulo  # Sumar 360 para convertir a ángulo con respecto a x.
        radio = math.sqrt(xc ** 2 + yc ** 2)
        return self.angulo_inicial <= angulo <= self.angulo_final and self.radio_interno <= radio <= self.radio_externo

    def desplazar_sistema_de_referencia(self, desp_x, desp_y):
        self.xg = self.xg + desp_x
        self.xc = self.xc + desp_x
        self.yg = self.yg + desp_y
        self.yc = self.yc + desp_y
        self.nodo_centro = Nodo(self.xc, self.yc)
        self.nodos_extremos = self.obtener_nodos_extremos()
        self.segmentos_rectos = self.obtener_segmentos_rectos()

    def plot(self, indice_color, espesor, ax, mostrar_centroide=False, transparencia=1):
        # Coordenadas de los puntos del trapecio circular
        color = random.choice(lista_colores) if indice_color is None else lista_colores[indice_color]
        espesor = random.choice(lista_espesores) if espesor is None else espesor
        style_data = {"color": color, "linewidth": espesor, "alpha": transparencia}

        arco_externo = Arc((self.xc, self.yc),
                           2 * self.radio_externo,
                           2 * self.radio_externo,
                           theta1=self.angulo_inicial,
                           theta2=self.angulo_final,
                           **style_data)
        if self.radio_interno > 0:
            arco_interno = Arc((self.xc, self.yc),
                               2 * self.radio_interno,
                               2 * self.radio_interno,
                               theta1=self.angulo_inicial,
                               theta2=self.angulo_final,
                               **style_data)
            ax.add_patch(arco_interno)

        ax.add_patch(arco_externo)
        if self.segmentos_rectos is not None:
            self.segmentos_rectos[0].plot(ax, **style_data)
            self.segmentos_rectos[1].plot(ax, **style_data)
        if mostrar_centroide:
            plt.scatter(self.xg, self.yg, c=color, marker=".", zorder=0)
        return ax
        # Crear la figura y el gráfico

class ContornoCircular(ElementoTrapecioCircular):
    tipo = "Circular"

    def __init__(self, nodo_centro: Nodo, indice, radios: tuple, angulos: tuple = (0, 360), signo=1):
        self.signo = signo
        super().__init__(nodo_centro, radios, angulos)

    def discretizar_contorno(self, discretizacion_angulo, discretizacion_radio):
        """Define la lista de elementos tipo ElementoRectangular que se encuentran dentro del contorno."""
        n = discretizacion_radio
        funcion_radios = lambda i: self.radio_interno + (self.radio_externo - self.radio_interno) * (
                    1 - (1 - i / n) ** 1.25)

        resultado = []
        radio = self.radio_interno
        radio_final = funcion_radios(1)
        i_inicial = 0

        if self.radio_interno == 0:  # Sector central circular
            resultado.append(ElementoTrapecioCircular(
                nodo_centro=self.nodo_centro,
                angulos=(self.angulo_inicial, self.angulo_final),
                radios=(radio, radio_final / 2)))
            angulo = self.angulo_inicial
            while angulo <= self.angulo_final - discretizacion_angulo:
                resultado.append(ElementoTrapecioCircular(
                    nodo_centro=self.nodo_centro,
                    angulos=(angulo, angulo + discretizacion_angulo),
                    radios=(radio_final / 2, radio_final)))
                angulo = angulo + discretizacion_angulo
            i_inicial = i_inicial + 1

        for i_radios in range(i_inicial, n):
            radio = funcion_radios(i_radios)
            radio_final = funcion_radios(i_radios + 1)
            angulo = self.angulo_inicial
            while angulo <= self.angulo_final - discretizacion_angulo:
                resultado.append(ElementoTrapecioCircular(
                    nodo_centro=self.nodo_centro,
                    angulos=(angulo, angulo + discretizacion_angulo),
                    radios=(radio, radio_final)))
                angulo = angulo + discretizacion_angulo
        return resultado

    def eliminar_elementos_fuera_de_contorno(self, lista_de_elementos):
        """Elimina aquellos elementos definidos preliminarmente que finalmente se encuentran por fuera del contorno."""
        for elemento_diferencial in lista_de_elementos.copy():
            if not self.nodo_en_elemento(elemento_diferencial.nodo_centroide):  # Fuera del semiplano del contorno.
                lista_de_elementos.remove(elemento_diferencial)
        return lista_de_elementos

    def mostrar_contorno_y_discretizacion(self, lista_elementos):
        # Contorno
        fig, ax = plt.subplots()
        for elemento in lista_elementos:
            elemento.plot(ax)
        plt.show()

class Contorno(Poligono):
    """Un contorno es un polígono el cual podrá ser discretizado en elementos tipo ElementoRectangular.
    Además, el atributo signo lleva asignado un significado:
    signo = -1; negativo implica que el contorno es un agujero en la sección final.
    signo = 1; por el contrario, positivo es un contorno con elementos que aportarán a la solución final."""
    tipo = "Poligonal"

    def __init__(self, nodos: list, signo: int, indice, ordenar=False):
        self.signo = signo
        self.indice = indice
        super().__init__(nodos, ordenar=ordenar)

    def discretizar_contorno(self, dx, dy):
        """Define la lista de elementos tipo ElementoRectangular que se encuentran dentro del contorno."""
        lista_de_elementos = self.obtener_lista_de_elementos_preliminar(dx, dy)
        lista_de_elementos.sort(key=lambda elemento: (elemento.y, elemento.x))
        # Elementos con coordenadas no válidas deben ser limpiados
        lista_elem_validos = [elem for elem in lista_de_elementos if not (math.isnan(elem.xg) or math.isnan(elem.yg))]
        return self.eliminar_elementos_fuera_de_contorno(
            lista_elem_validos)  # Limpieza adicional de elementos no validos.

    def obtener_lista_de_elementos_preliminar(self, dx, dy):
        """Obtiene la lista de elementos preliminarmente del rectángulo mayor que contiene al contorno, para luego
         eliminar los restantes."""
        lista_de_elementos = []
        lista_de_direcciones = [(1, 1), (1, -1), (-1, -1), (-1, 1)]
        for direccion in lista_de_direcciones:
            x_partida = self.xg + direccion[0] * dx / 2
            y_partida = self.yg + direccion[1] * dy / 2
            lista_de_elementos = lista_de_elementos + self.elementos_segun_direccion(x_partida, y_partida, dx, dy,
                                                                                direccion)
        return lista_de_elementos

    def elementos_segun_direccion(self, x_partida, y_partida, dx, dy, direccion: tuple):
        """La direccion de avance viene dada por una tupla tipo (±1; ±1), el primer término indicando si es positivo
        en X y el segundo si lo es en Y."""
        x_max, y_max = max(self.x), max(self.y)
        x_min, y_min = min(self.x), min(self.y)
        lista_de_elementos = []
        condicion_y = lambda yp: yp - dy / 2 < y_max if direccion[1] == 1 else yp + dy / 2 > y_min
        condicion_x = lambda xp: xp - dx / 2 < x_max if direccion[0] == 1 else xp + dx / 2 > x_min
        x = x_partida
        y = y_partida
        while condicion_y(y):
            while condicion_x(x):
                rectangulo_diferencial = ElementoRectangular(ubicacion_centro=Nodo(x, y), medidas=(dx, dy))
                rectangulo_diferencial = rectangulo_diferencial.obtener_poligono_interseccion(
                    self)  # recortando con bordes de contorno
                lista_de_elementos.append(rectangulo_diferencial)
                x = x + direccion[0] * dx
            y = y + direccion[1] * dy
            x = x_partida
        return lista_de_elementos

    def eliminar_elementos_fuera_de_contorno(self, lista_de_elementos):
        """Elimina aquellos elementos definidos preliminarmente que finalmente se encuentran por fuera del contorno."""
        for i, nodo in enumerate(self.nodos_extremos):
            nodo_1, nodo_2, nodo_3 = self.obtener_3_nodos_x_indice(i)
            recta = Recta(nodo_1, nodo_2)  # Recta definida por el nodo 1 y 2.
            # La ecuación de la recta devuelve números del mismo signos para puntos en el mismo semiplano
            signo_semiplano_contorno = recta.ecuacion_recta(nodo_3)
            if signo_semiplano_contorno == 0:
                raise Exception(
                    f"Por favor, ingresar puntos en el contorno que no estén alineados. Contorno: {self.indice}")
            for elemento_diferencial in lista_de_elementos.copy():
                signo_semiplano_elemento = recta.ecuacion_recta(elemento_diferencial.nodo_centroide)
                if signo_semiplano_contorno * signo_semiplano_elemento < 0:  # Fuera del semiplano del contorno.
                    lista_de_elementos.remove(elemento_diferencial)
        return lista_de_elementos

    def mostrar_contorno_y_discretizacion(self, lista_elementos):
        # Contorno
        X = [nodo.x for nodo in self.nodos_extremos]
        Y = [nodo.y for nodo in self.nodos_extremos]
        plt.plot(X, Y)
        for elemento in lista_elementos:
            elemento.plot(indice_color=0, espesor=0.5)
        self.plot(indice_color=1)
        plt.title("Contorno y Discretizacion - pre eliminar elementos negativos")
        plt.show()

class SeccionArbitraria(object):
    """Una Sección Genérica será la combinación de objetos tipo Contorno, dando el resultante dependiendo del signo,
    de los mismos."""

    def __init__(self, contornos: dict, dx, dy):
        self.dx, self.dy = dx, dy
        self.contornos_negativos = [contorno for indice_contorno, contorno in contornos.items() if contorno.signo == -1]
        self.contornos_positivos = [contorno for indice_contorno, contorno in contornos.items() if contorno.signo == 1]
        self.elementos = self.obtener_matriz_elementos_positivos()
        self.area, self.xg, self.yg = self.obtener_baricentro_y_area()
        self.cambiar_coordenadas_a_baricentro()
        self.x_min = min(x.xg for x in self.elementos)
        self.x_max = max(x.xg for x in self.elementos)
        self.y_min = min(x.yg for x in self.elementos)
        self.y_max = max(x.yg for x in self.elementos)

    def obtener_matriz_elementos_positivos(self):
        result = []
        for contorno_positivo in self.contornos_positivos:
            if contorno_positivo.tipo == "Poligonal":
                lista_elementos_positivos = contorno_positivo.discretizar_contorno(self.dx, self.dy)
            else:
                lista_elementos_positivos = contorno_positivo.discretizar_contorno(discretizacion_angulo=5,
                                                                                discretizacion_radio=50)
            for elemento_positivo in lista_elementos_positivos:
                if self.elemento_pertenece_a_contorno_negativo(elemento_positivo):
                    continue  # Descartar elemento
                elemento_intersectado = self.obtener_interseccion_elemento_positivo_con_negativo(elemento_positivo)
                if elemento_intersectado:
                    result.append(elemento_intersectado)
        return result

    def elemento_pertenece_a_contorno_negativo(self, elemento_positivo: Poligono):
        for contorno_negativo in self.contornos_negativos:
            if elemento_positivo < contorno_negativo:  # Pertenece COMPLETAMENTE a elemento
                return True
        return False

    def obtener_interseccion_elemento_positivo_con_negativo(self, elemento_positivo):
        for contorno_neg in self.contornos_negativos:
            elemento_positivo = elemento_positivo.restar_con_otro_poligono(contorno_neg)
            if elemento_positivo is None:
                return None
        return elemento_positivo

    def mostrar_contornos_2d(self, ax):
        for contorno_negativo in self.contornos_negativos:
            contorno_negativo.plot(indice_color=2, espesor=2, ax=ax)
        for contorno_positivo in self.contornos_positivos:
            contorno_positivo.plot(ax=ax, indice_color=2, espesor=2)

    def mostrar_contornos_2d_plotly(self, fig):
        plotly_util = PlotlyUtil()
        plotly_util.plot_poligono(fig=fig, lista_elementos=self.contornos_negativos, color="Red", espesor=3, transparencia=1)
        plotly_util.plot_poligono(fig=fig, lista_elementos=self.contornos_positivos, color="Cyan", espesor=4, transparencia=1)

    def mostrar_discretizacion_2d(self, ax):
        for elemento in self.elementos:
            elemento.plot(indice_color=3, espesor=0.2, mostrar_centroide=True, ax=ax, transparencia=0.1)

    def mostrar_discretizacion_2d_plotly(self, fig):
        plotly_util = PlotlyUtil()
        plotly_util.plot_poligono(fig=fig, lista_elementos=self.elementos, color="Cyan", transparencia=0.2, mostrar_centroide=True)

    def mostrar_contornos_3d(self, ecuacion_plano_a_desplazar=None):
        fig = plt.figure()
        ax = fig.add_subplot(projection='3d')
        for contorno_negativo in self.contornos_negativos:
            contorno_negativo.plot(
                indice_color=2, espesor=2, tridimensional=True, ec_plano_3d=ecuacion_plano_a_desplazar,
                plt_3d=ax)
        for contorno_positivo in self.contornos_positivos:
            contorno_positivo.plot(
                indice_color=1, espesor=2, tridimensional=True, ec_plano_3d=ecuacion_plano_a_desplazar,
                plt_3d=ax)

    def mostrar_discretizacion_3d_desplazada(self):
        for elemento in self.elementos:
            elemento.cargar_poligono_para_mostrar(indice_color=3, espesor=2, mostrar_centroide=True)

    def obtener_baricentro_y_area(self):
        area_total = 0
        sx = 0
        sy = 0
        for elemento in self.elementos:
            area_total = area_total + elemento.area
            sx = sx + elemento.area * elemento.xg
            sy = sy + elemento.area * elemento.yg
        return round(area_total, 10), round(sx / area_total, 10), round(sy / area_total, 10)

    def cambiar_coordenadas_a_baricentro(self):
        for elemento in self.elementos:
            elemento.desplazar_sistema_de_referencia(desp_x=-self.xg, desp_y=-self.yg)
        for contorno_pos in self.contornos_positivos:
            contorno_pos.desplazar_sistema_de_referencia(desp_x=-self.xg, desp_y=-self.yg)
        for contorno_neg in self.contornos_negativos:
            contorno_neg.desplazar_sistema_de_referencia(desp_x=-self.xg, desp_y=-self.yg)

    @staticmethod
    def plot_to_buf(lista_de_elementos, height=2800, width=2800, inc=0.3):
        data = np.array([(elemento.xg, elemento.yg) for elemento in lista_de_elementos])
        xlims = (data[:, 0].min(), data[:, 0].max())
        ylims = (data[:, 1].min(), data[:, 1].max())
        dxl = xlims[1] - xlims[0]
        dyl = ylims[1] - ylims[0]

        print('xlims: (%f, %f)' % xlims)
        print('ylims: (%f, %f)' % ylims)

        buffer = np.zeros((height + 1, width + 1))
        for i, p in enumerate(data):
            print('\rloading: %03d' % (float(i) / data.shape[0] * 100), end=' ')
            x0 = int(round(((p[0] - xlims[0]) / dxl) * width))
            y0 = int(round((1 - (p[1] - ylims[0]) / dyl) * height))
            buffer[y0, x0] += inc
            if buffer[y0, x0] > 1.0:
                buffer[y0, x0] = 1.0
        return xlims, ylims, buffer

    def obtener_rango_valores_extremos(self):
        lista_x = []
        lista_y = []
        for contorno in self.contornos_positivos:
            for nodo in contorno.nodos_extremos:
                lista_x.append(nodo.x)
                lista_y.append(nodo.y)
        return ((min(lista_x), max(lista_x)), (min(lista_y), max(lista_y)))


from . import user
from main import db
from schemas.users import (
    UserSignUpSchema,
    UserSchema
)
from models.users import Role

from flask import (
    request
)
from loguru import logger

@user.post('/')
def sign_up():
    schema = UserSignUpSchema()
    data = request.get_json(force=True)
    try:
        new_user = schema.load(data)
        # persist to database
        try:
            db.session.add(new_user)
            db.session.commit()

            resp = UserSchema().dump(new_user)

        except Exception as e:
            logger.error(e)
            db.session.rollback()
        finally:
            db.session.close()

    except Exception as e:
        return {
            'status': 'error',
            'message': str(e),
            'data': {}
        }

    return {
        'status': 'success',
        'message': 'sign up successful',
        'data': resp
    }, 201

@user.patch('/')
def update_info():
    pass


import sqlite3
import openpyxl
import datetime
import os
import settings
from openpyxl.styles.fonts import Font
from openpyxl.styles.borders import Border, Side
from openpyxl.styles import Alignment
from openpyxl.styles import PatternFill

rss_new_url = settings.rss_new_url
rss_blog = settings.rss_blog
rss_blog_url = settings.rss_blog_url
dbname = settings.sqlite_dbname
now = datetime.datetime.now()
filename = settings.output_path + 'aws-rss_' + now.strftime('%Y%m%d.%H%M%S') + '.xlsx'

def main():
    rssawsnew_excel()
    if rss_blog == "true":
        rssawsblog_excel()

def rssawsnew_excel():
    workbook = openpyxl.Workbook()
    sheet1 = workbook.active
    sheet1.title = 'Work(Whats New)'

    border = Border(top=Side(style='thin', color='000000'),
        bottom=Side(style='thin', color='000000'),
        left=Side(style='thin', color='000000'),
        right=Side(style='thin', color='000000')
    )

    fill = PatternFill(patternType='solid',fgColor='00FFFF')

    sheet1['A2'].value = 'No.'
    sheet1['B2'].value = '投稿日'
    sheet1['C2'].value = 'サービス'
    sheet1['D2'].value = 'アップデートタイトル'
    sheet1['E2'].value = '概要'
    sheet1['F2'].value = '調査対象'
    sheet1.auto_filter.ref = "A2:F2"

    for abc in ['A','B','C','D','E','F']:
        sheet1[abc + '2'].font = Font(name='Meiryo',size='10',bold=True)
        sheet1[abc + '2'].border = border
        sheet1[abc + '2'].alignment = Alignment(horizontal='center', vertical='center')
        sheet1[abc + '2'].fill = fill

    db = sqlite3.connect(dbname)
    cur = db.cursor()
    cur.execute('SELECT no,published,category,title,url,summary FROM rss_new_table order by published desc')
    for i, row in enumerate (cur):
        sheet1['A' + str(i+3)].value =  (i+1)
        sheet1['B' + str(i+3)].value = row[1]
        sheet1['C' + str(i+3)].value = row[2]
        sheet1['D' + str(i+3)].value = row[3]
        sheet1['D' + str(i+3)].hyperlink = row[4]
        sheet1['D' + str(i+3)].style = "Hyperlink"
        sheet1['E' + str(i+3)].value = row[5]

    cur.close()

    for rows0 in sheet1['A3':'A1000']:
        for cell0 in rows0:
            cell0.font = Font(name='Meiryo',size='9')
            cell0.border = border
            cell0.alignment = Alignment(horizontal='center', vertical='center')

    for rows1 in sheet1['B3':'C1000']:
        for cell1 in rows1:
            cell1.font = Font(name='Meiryo',size='9')
            cell1.border = border
            cell1.alignment = Alignment(horizontal='left', vertical='center', wrapText=True)

    for rows2 in sheet1['D3':'D1000']:
        for cell2 in rows2:
            cell2.font = Font(name='Meiryo',size='9',color='0000FF')
            cell2.border = border
            cell2.alignment = Alignment(horizontal='left', vertical='top', wrapText=True)

    for rows3 in sheet1['E3':'E1000']:
        for cell3 in rows3:
            cell3.font = Font(name='Meiryo',size='9')
            cell3.border = border
            cell3.alignment = Alignment(horizontal='left', vertical='top', wrapText=True)

    for rows4 in sheet1['F3':'F1000']:
        for cell4 in rows4:
            cell4.font = Font(name='Meiryo',size='9')
            cell4.border = border
            cell4.alignment = Alignment(horizontal='center', vertical='center')

    sheet1.column_dimensions['A'].width = 4
    sheet1.column_dimensions['B'].width = 12
    sheet1.column_dimensions['C'].width = 20
    sheet1.column_dimensions['D'].width = 55
    sheet1.column_dimensions['E'].width = 82
    sheet1.column_dimensions['F'].width = 12
    sheet1.freeze_panes = 'A3'

    workbook.save(filename)

def rssawsblog_excel():
    workbook = openpyxl.load_workbook(filename)
    workbook.create_sheet( title="Work(AWS Blog)" )
    sheet2 = workbook['Work(AWS Blog)']

    border = Border(top=Side(style='thin', color='000000'),
        bottom=Side(style='thin', color='000000'),
        left=Side(style='thin', color='000000'),
        right=Side(style='thin', color='000000')
    )

    fill = PatternFill(patternType='solid',fgColor='00FFFF')

    sheet2['A2'].value = 'No.'
    sheet2['B2'].value = '投稿日'
    sheet2['C2'].value = 'サービス'
    sheet2['D2'].value = 'アップデートタイトル'
    sheet2['E2'].value = '概要'
    sheet2['F2'].value = '調査対象'
    sheet2.auto_filter.ref = "A2:F2"

    for abc in ['A','B','C','D','E','F']:
        sheet2[abc + '2'].font = Font(name='Meiryo',size='10',bold=True)
        sheet2[abc + '2'].border = border
        sheet2[abc + '2'].alignment = Alignment(horizontal='center', vertical='center')
        sheet2[abc + '2'].fill = fill

    db = sqlite3.connect(dbname)
    cur = db.cursor()
    cur.execute('SELECT no,published,category,title,url,summary FROM rss_blog_table order by published desc')
    for i, row in enumerate (cur):
        sheet2['A' + str(i+3)].value =  (i+1)
        sheet2['B' + str(i+3)].value = row[1]
        sheet2['C' + str(i+3)].value = row[2]
        sheet2['D' + str(i+3)].value = row[3]
        sheet2['D' + str(i+3)].hyperlink = row[4]
        sheet2['D' + str(i+3)].style = "Hyperlink"
        sheet2['E' + str(i+3)].value = row[5]

    cur.close()

    for rows0 in sheet2['A3':'A1000']:
        for cell0 in rows0:
            cell0.font = Font(name='Meiryo',size='9')
            cell0.border = border
            cell0.alignment = Alignment(horizontal='center', vertical='center')

    for rows1 in sheet2['B3':'C1000']:
        for cell1 in rows1:
            cell1.font = Font(name='Meiryo',size='9')
            cell1.border = border
            cell1.alignment = Alignment(horizontal='left', vertical='center', wrapText=True)

    for rows2 in sheet2['D3':'D1000']:
        for cell2 in rows2:
            cell2.font = Font(name='Meiryo',size='9',color='0000FF')
            cell2.border = border
            cell2.alignment = Alignment(horizontal='left', vertical='top', wrapText=True)

    for rows3 in sheet2['E3':'E1000']:
        for cell3 in rows3:
            cell3.font = Font(name='Meiryo',size='9')
            cell3.border = border
            cell3.alignment = Alignment(horizontal='left', vertical='top', wrapText=True)

    for rows4 in sheet2['F3':'F1000']:
        for cell4 in rows4:
            cell4.font = Font(name='Meiryo',size='9')
            cell4.border = border
            cell4.alignment = Alignment(horizontal='center', vertical='center')

    sheet2.column_dimensions['A'].width = 4
    sheet2.column_dimensions['B'].width = 12
    sheet2.column_dimensions['C'].width = 20
    sheet2.column_dimensions['D'].width = 55
    sheet2.column_dimensions['E'].width = 82
    sheet2.column_dimensions['F'].width = 12
    sheet2.freeze_panes = 'A3'

    workbook.save(filename)

if __name__ == "__main__":
    main()