from datetime import datetime, timezone, timedelta
import requests
import json

class GCoreAccount:
    def __init__(self, api_key):
        self.__api_key = api_key
        self.__default_headers = {
            "Authorization": "APIKey " + self.__api_key
        }

    def get_cdn_service_details(self):
        host = "https://api.gcorelabs.com/cdn/clients/me"
        response = requests.get(host, headers=self.__default_headers)
        return json.loads(response.text)

    def get_all_cdn_resources(self):
        host = "https://api.gcorelabs.com/cdn/resources"
        response = requests.get(host, headers=self.__default_headers)
        return json.loads(response.text)

    def get_cdn_id_by_domain(self, domain):
        all_resources = self.get_all_cdn_resources()
        for resource in all_resources:
            if resource["cname"] == domain:
                return resource["id"]
        return None

    def get_cdn_30_days_traffic(self, resource_id=None):
        host = "https://api.gcorelabs.com/cdn/statistics/aggregate/stats"
        utc_time_now = datetime.now(timezone.utc)
        start_time = (utc_time_now - timedelta(days=30)).strftime("%Y-%m-%dT%H:00:00")
        end_time = utc_time_now.strftime("%Y-%m-%dT%H:00:00")
        if resource_id is None:
            response = requests.get(host, headers=self.__default_headers,
                                    params={"service": "CDN",
                                            "from": start_time,
                                            "to": end_time,
                                            "metrics": "sent_bytes",
                                            "group_by": "resource"})
        else:
            response = requests.get(host, headers=self.__default_headers,
                                    params={"service": "CDN",
                                            "from": start_time,
                                            "to": end_time,
                                            "metrics": "sent_bytes",
                                            "group_by": "resource",
                                            "resource": resource_id})
        return json.loads(response.text)

    def get_cdn_30_day_traffic_by_domain(self, domain):
        target_id = self.get_cdn_id_by_domain(domain)
        result = self.get_cdn_30_days_traffic(resource_id=target_id)
        return result

    def get_remaining_traffic(self):
        TOTAL_TRAFFIC = 1099511627776
        total_traffic_bytes = 0
        result = self.get_cdn_30_days_traffic()["resource"]
        for resource_id in result.keys():
            total_traffic_bytes += result[resource_id]["metrics"]["sent_bytes"]
        return TOTAL_TRAFFIC - total_traffic_bytes

    def get_remaining_traffic_percentage(self):
        return round(self.get_remaining_traffic() / 1099511627776, 4)


import torch
import sys
import argparse

if __name__ != "Config":
    # if "Config" in sys.modules:
    #     class doubleImport(ImportError):
    #         """
    #         Config was imported using a different path after it has already been imported.
    #         This causes problems when Config is modified.
    #         """
    #         pass
    #     raise doubleImport
    print(f"A POSSIBLE PROBLEM HAS OCCURED, Config was loaded improperly, from {__name__} instead of directly\
    this might break some global variables by having two copies",file=sys.stderr)

#TODO: Rework config so that it is less janky and uses less bad practices of global variables.
# Possibly by moving HelperFunctions Loop functions to outside of the program
# and just using the command line parser for the individual sections.

#This config file is mainly used as global variables for the rest of the program.
#It should only be modified by the loop commands in helperfunctions

def loopOverUnknowns(unknownlist=False):
    """
    Given a list of unknowns (integers 0-14) this will create a list of knowns (the inverted list).
    """
    if unknownlist == False:
        unknownlist = parameters["Unknowns_clss"][0]
    knownVals = list(range(parameters["CLASSES"][0]))
    notused = unknownlist + UnusedClasses
    notused.sort()
    for un in notused:
        if un in knownVals:
            knownVals.remove(un)

    if len(unknownlist) > parameters["CLASSES"][0] -3:
        print("Too many unknowns, some algorithms might not work")
    if len(knownVals)<2:
        print("Too few knowns, things might break")
    parameters["Unknowns"] = f"{len(unknownlist)} Unknowns"
    parameters["Unknowns_clss"] = [unknownlist,"Values used for testing"]
    parameters["Knowns_clss"] = [knownVals,"Values used for training"]
    return knownVals

#This is the diffrent optimization functions
opt_func = {"Adam":torch.optim.Adam,"SGD":torch.optim.SGD, "RMSprop":torch.optim.RMSprop}

#Here are all of the paremeters for the model.
parameters = {
    #These parameters are orginized like this:
    #"ParamName":[Value,"Description",[possible values]]
    #for a parameter called "ParamName" with a value of Value
    "batch_size":[100000, "Number of items per batch"],
    "num_workers":[14, "Number of threads working on building batches"],
    "attemptLoad":[0, "0: do not use saves\n1:use saves"],
    "testlength":[1/4, "[0,1) percentage of training to test with"],
    "Mix unknowns and validation": [1,"0 or 1, 0 means that the test set is purely unknowns and 1 means that the testset is the validation set plus unknowns (for testing)"],
    "MaxPerClass": [1000, "Maximum number of samples per class\n if Dataloader_Variation is Cluster and this value is a float it interprets it as the maximum percentage of the class instead."],
    "num_epochs":[150,"Number of times it trains on the whole trainset"],
    "learningRate":[0.001, "a modifier for training"],
    "threshold":[0.5,"When to declare something to be unknown"],
    "model":["Convolutional","Model type",["Fully_Connected","Convolutional"]],
    "OOD Type":["Soft","type of out of distribution detection", ["Soft","Open","Energy","COOL","DOC","iiMod"]],
    "Dropout":[0.1,"percent of nodes that are skipped per run, larger numbers for more complex models [0,1)"],
    "Dataloader_Variation":["Standard","Defines the style of Dataloader used. This affects sampling from the dataset", ["Standard","Cluster","Flows"]],
    "optimizer":opt_func["Adam"],
    "Unknowns":["UNUSED"],
    "Unknowns_clss": [[7,8,9],"Class indexes used as unknowns."],
    "CLASSES":[15,"Number of classes, do not change"],
    "Temperature":[1,"Energy OOD scaling parameter"],
    "Degree of Overcompleteness": [3,"Parameter for Fitted Learning"],
    "Number of Layers": [3,"Number of layers to add to the base model"],
    "Nodes": [512,"The number of nodes per added layer"],
    "Activation": ["Leaky","The type of activation function to use",["ReLU", "Tanh", "Sigmoid","Leaky"]],
    "LOOP": [0,"This is a parameter that determines if we want to loop over the algorithms.\n "\
    "0: no loop, 1:loop through variations of algorithms,thresholds,learning rates, groups and numbers of epochs, \n"\
    "2: Loop while adding more unknowns into the training data (making them knowns) without resetting the model, \n"\
    "3: Loop through different data distributions without training the model.\n"\
    "4: Loop through predefined hyperparameters found in datasets/hyperparamList.csv"],
    "Dataset": ["Payload_data_CICIDS2017", "This is what dataset we are using,", ["Payload_data_CICIDS2017","Payload_data_UNSW"]],
    "SchedulerStepSize": [10, "This is how often the scheduler takes a step, 3 means every third epoch"],
    "SchedulerStep": [0.9,"This is how big a step the scheduler takes, leave 0 for no step"]
}

#Argparse tutorial: https://docs.python.org/3/howto/argparse.html
parser = argparse.ArgumentParser()
for x in parameters.keys():
    if x in ["batch_size","num_workers","MaxPerClass","num_epochs","Degree of Overcompleteness","Number of Layers","Nodes","SchedulerStepSize"]:
        parser.add_argument(f"--{x}",type=int,default=parameters[x][0],help=parameters[x][1],required=False)
    if x in ["testlength","learningRate","threshold","Dropout","Temperature","SchedulerStep"]:
        parser.add_argument(f"--{x}",type=float,default=parameters[x][0],help=parameters[x][1],required=False)
    if x in ["attemptLoad","Mix unknowns and validation"]:
        parser.add_argument(f"--{x}",type=int,choices=[1,0],default=parameters[x][0],help=parameters[x][1],required=False)
    if x in ["LOOP"]:
        parser.add_argument(f"--{x}",type=int,choices=[0,1,2,3,4],default=parameters[x][0],help=parameters[x][1],required=False)
    if x in ["model","OOD Type","Dataloader_Variation","Activation","Dataset"]:
        parser.add_argument(f"--{x}",choices=parameters[x].pop(),default=parameters[x][0],help=parameters[x][1],required=False)
    if x in ["Unknowns_clss"]:
        parser.add_argument(f"--{x}",default=f"{parameters[x][0]}",help=parameters[x][1],required=False)
if "pytest" not in sys.modules: #The argument parser appears to have issues with the pytest tests. I have no idea why.
    args = parser.parse_args()
    for x in args._get_kwargs():
        parameters[x[0]][0] = x[1]

if isinstance(parameters["Unknowns_clss"][0],str):
    if len(parameters["Unknowns_clss"][0])>0 and len(parameters["Unknowns_clss"][0])!=2: #Not sure why I need this specifier but it breaks if the default is []
        # print(len(parameters["Unknowns_clss"][0]))
        parameters["Unknowns_clss"][0] = [int(y) for y in parameters["Unknowns_clss"][0].removesuffix("]").removeprefix("[").split(sep=",")]
    else:
        parameters["Unknowns_clss"][0] = []

DOC_kernels = [3,4,5]

#Set Number of classes:
if parameters["Dataset"][0] == "Payload_data_UNSW":
    parameters["CLASSES"][0] = 10
    UnusedClasses = []
else:
    UnusedClasses = [8,9,10]
UnusedClasses = []

#Dendrogram chunk uses a slightly diffrent output on the model structure.
# (Also, dendrogram chunk is not working, so don't use it. Possibly related.)
if parameters["Dataloader_Variation"][0] == "Old_Cluster":
    parameters["CLASSES"][0] = parameters["CLASSES"][0] *32

#Add a value to the dictionary that is the inverse of the unknowns
loopOverUnknowns()

#This is for saving the original number of epochs
num_epochs = parameters["num_epochs"][0]

#This is to test all of the algorithms one after the other. (Loop 1 values)
alg = ["Soft","Open","Energy","COOL","DOC","iiMod"]
batch = [100,1000,10000,100000]
datapoints_per_class = [10,100,1000]
thresholds = [0.1,1,10]
thresholds = [30,20,15,5]
thresholds = [parameters["threshold"][0]]
learning_rates = [0.1,0.01,0.001,0.0001]
activation = ["ReLU", "Tanh", "Sigmoid","Leaky"]
groups = [[],[2],[2,3],[2,3,4],[2,3,4,5],[2,3,4,5,6],[2,3,4,5,6,7],[1,2,3,4,5,6,7],[1,2,3,4,5,6,7,8]]
#groups = [[7,8,9]]
if parameters["Dataset"][0] == "Payload_data_CICIDS2017":
    incGroups = [[2,3,4,5,6,7,8,9,10,11,12,13,14],[3,4,5,6,7,8,9,10,11,12,13,14],[4,5,6,7,8,9,10,11,12,13,14],[5,6,7,8,9,10,11,12,13,14],[6,7,8,9,10,11,12,13,14],[7,8,9,10,11,12,13,14],[8,9,10,11,12,13,14],[9,10,11,12,13,14],[10,11,12,13,14],[11,12,13,14],[12,13,14],[13,14],[14]]
#This one list is for loop 2. Note: array size should be decreasing.
else:
    incGroups = [[2,3,4,5,6,7,8,9],[3,4,5,6,7,8,9],[4,5,6,7,8,9],[5,6,7,8,9],[6,7,8,9],[7,8,9],[8,9],[9]]
epochs= []
epochs = [1,10,100,150]

# groups = [list(range(2,parameters["CLASSES"][0]))]
# #Little bit of code that generates incremental numbers of unknowns.
# while len(groups[0])>2:
#     new = groups[0].copy()
#     new.pop(0)
#     new.pop(0)
#     groups.insert(0,new)
# #Little bit of code that generates decrementing numbers of unknowns.
# incGroups = [list(range(2,parameters["CLASSES"][0]))]
# while len(incGroups[-1])>1:
#     new = incGroups[-1].copy()
#     new.pop(0)
#     incGroups.append(new)

#Here is where we remove some of the algorithms if we want to skip them. We could also just remove them from the list above.
# alg.remove("Soft")
# alg.remove("Open")
# alg.remove("Energy")
# alg.remove("COOL")
# alg.remove("DOC")
# alg.remove("iiMod")

#it causes problems if you dont start at the start of the loop.
if parameters["LOOP"][0] == 1:
    parameters["OOD Type"][0] = alg[0]

#Optimizer has been removed from the list of things we are changing
optim = [opt_func["Adam"], opt_func["SGD"], opt_func["RMSprop"]]
optim = [opt_func["Adam"]]

#Adds in everything in config:

# #learning_rates.remove(Config.parameters["learningRate"][0])
# learning_rates.insert(0,parameters["learningRate"][0])
# #epochs.remove(Config.parameters["num_epochs"][0])
# epochs.insert(0,parameters["num_epochs"][0])
# groups.insert(0,helper_variables["unknowns_clss"])

# #Always starts with the configured activation type
# alg.remove(parameters["OOD Type"][0])
# alg.insert(0,parameters["OOD Type"][0])

#This is an array to eaiser loop through everything.
loops = [batch,learning_rates,activation,["Standard","Cluster"],groups]
# loops = [groups]
loops2 = ["batch_size","learningRate","Activation","Dataloader_Variation","Unknowns"]
# loops2 = ["Unknowns"]
for i in range(len(loops)):
    if loops2[i] == "Unknowns":
        loops[i].insert(0,parameters["Unknowns_clss"][0])
    elif loops2[i] == "optimizer":
        loops[i].insert(0,parameters[loops2[i]])
    elif loops2[i] == "None":
        pass
    else:
        loops[i].insert(0,parameters[loops2[i]][0])

#Override the unknowns because model is kept
if parameters["LOOP"][0] == 2:
    parameters["Unknowns_clss"][0] = incGroups[0]
    parameters["Unknowns"] = f"{incGroups[0]} Unknowns"
    parameters["Knowns_clss"][0] = loopOverUnknowns()

#This controls all of the save data (so that we can run small tests without changing the nice files.)
unit_test_mode = False

use_alg_thesholds =False
def algorithmSpecificSettings(alg="None"):
    if use_alg_thesholds == False:
        return
    if alg == "None":
        alg = parameters["OOD Type"][0]

    # match alg:
    if alg == "Soft":
        pass
    if alg == "Open":
        parameters["threshold"][0] = 0.8
    if alg == "Energy":
        parameters["threshold"][0] = 0.474
    if alg == "COOL":
        parameters["threshold"][0] = 0.516034961
    if alg == "DOC":
        parameters["threshold"][0] = 0.06449493
    if alg == "iiMod":
        parameters["threshold"][0] = 102064.4453

if parameters["LOOP"][0] == 3:
    # parameters["num_epochs"][0] = 0
    parameters["loopLevel"] = [0,"What percentages the model is on"]
    parameters["MaxSamples"] = [parameters["MaxPerClass"][0], "Max number of samples total"]

#Getting version number
#https://gist.github.com/sg-s/2ddd0fe91f6037ffb1bce28be0e74d4e
f = open("build_number.txt","r")
parameters["Version"] = [f.read(),"The version number"]

save_as_tensorboard = True
datasetRandomOffset =True
dataparallel = True


from flask.views import MethodView
from flask_smorest import Blueprint, abort
from config.db import db
from models import User,Order as OrderModel
from schemas import OrderSchema
from sqlalchemy.exc import SQLAlchemyError

blp = Blueprint("Orders", "orders", description="Operations on orders")

@blp.route("/order")
class OrderList(MethodView):

    # @jwt_required()
    @blp.response(200,OrderSchema(many=True))
    def get(self):
        return OrderModel.query.all()

    @blp.arguments(OrderSchema)
    @blp.response(201,OrderSchema)
    def post(self,order_data):
        order = OrderModel(**order_data)
        try:
            db.session.add(order)
            db.session.commit()
            print(db)
        except SQLAlchemyError:
            abort(500, message="An error occured while processing order")
        return order

@blp.route("/order/<int:order_id>")
class OrderClass(MethodView):

    # @jwt_required()
    @blp.response(200,OrderSchema)
    def get(self,order_id):
        # app.logger.info('Info level log')
        # app.logger.warning('Warning level log')
        order = OrderModel.query.get_or_404(order_id)
        return order

    # @jwt_required()
    def delete(self,order_id):
        order = OrderModel.query.get_or_404(order_id)
        db.session.delete(order)
        db.session.commit()
        return {"message":"order deleted."}

    # @jwt_required()
    # @blp.arguments(PostUpdateSchema)
    # @blp.response(200,PostSchema)
    # def put(self,post_data,post_id):
    #     post =  PostModel.query.get(post_id)
    #     if post:
    #         post.title = post_data["title"]
    #         post.body = post_data["body"]
    #     # else:
    #     #     post =PostModel(id = post_id,**post_data)
    #     db.session.add(post)
    #     db.session.commit()
    #     return post


import numpy as np, pandas as pd
import healpy as hp
import six
import os
from healpy.sphtfunc import Alm
from healpy import cookbook as cb
import astropy.io.fits as pf
from numpy import asarray as ar

def index_of(arrval, value):
    if value < min(arrval): return 0
    return max(np.where(arrval<=value)[0])

def nearest_idx(array, value):
    return (np.abs(array-value)).argmin()

def decon_beam(Commap, cl, ell ,noise_range=None, add_back=True):
    '''
        deconvolution from beam transfer function and
        cancel out the noise based on the input l range
        assume-> Signal = W*Sky + Nise; W = BeamTF**2 * (pixel window function)**2
        Parameters:
        -----
        Commap: given compoenet map's fits filename
        noise: (lmin, lmax) tuple, average this range of noise
        ell: given ell you want to deconvolve

        reutrn cl
        '''
    # get bin width from ells
    bin = np.int(ell[1] - ell[0])

    # subtract the noise
    if noise_range:
        noise = cl[index_of(ell, noise_range[0]):index_of(ell, noise_range[1])].mean()
        cl = cl - noise

        if add_back==True:
            # artifact add back the mean power of transfered best-fit cl on noise range
            cl = artifact_addback(Commap, cl, ell, noise_range)

    # get beamTF from commap fits
    beam = np.float64(hp.mrdfits(Commap,hdu=2))[0]

    # pixel window function
    NSIDE = 2048;
    pl = hp.pixwin(NSIDE); pl = pl[0:4001]

    ## resample based on given ell
    lmax = ell[-1]; lmax = np.int(lmax)
    pl = pl[0:lmax+bin:bin]
    beam = beam[0:lmax+bin:bin]

    ## becareful about the index here,
    ## by python's convention it will ignore the last element.
    return cl/beam**2./pl**2.

def artifact_addback(Commap, cl, ell, noise_range):
    '''artificially put back the mean power of cls on the tail of noise subtraction,
    the putting back mean power is the mean of the same range from transfer-function convolved best-fit ΛCDM model.
    Parameters:
    Commap: Component map's path
    cl: cl (not dl) you want to add back, assume to be numpy array
    ell: angular size
    noise_range: a tuple (lmin, lmax)

    return cl
    '''
    # beam transfer function
    beam = np.float64(hp.mrdfits(Commap,hdu=2))[0]

    # read best-fit
    best = np.loadtxt('/Users/cicero/Documents/Physics/PLANCK/Release2/COM_PowerSpect_CMB-base-plikHM-TT-lowTEB-minimum-theory_R2.02.txt')
    L = best.T[0]
    CL = (best.T[1] / L / (L + 1) * 2 * np.pi) * beam[2:len(L)+2]**2.

    # rescalling
    L = L[0:int(max(ell) + ell[1]-ell[0]):int(ell[1]-ell[0])]
    CL = CL[0:int(max(ell) + ell[1]-ell[0]):int(ell[1]-ell[0])]

    # mean power on noise range
    artifact_power = np.mean(CL[index_of(L, noise_range[0]):index_of(L, noise_range[1])])

    try: cl = cl + artifact_power
    except: cl = [c + artifact_power for c in cl]
    return cl

def read_glesp(filename, return_cos=False):
    import pyfits
    '''
        filename: str, glesp format map
        return_cos: if True, return cos(theta(rad)), too. default is False.
        return
        gcanvas: gleasp map in matrix format (x = phi,y = -theta)
    '''
    glesp = pyfits.open(filename)
    data = glesp[1].data
    gtheta = data['COS(THETA)'][0]
    gcanvas = data['TEMPERATURE'][0]
    dim = len(gtheta)                       # get the (half) length
                                            # of pixel for one theta
    gcanvas = gcanvas.reshape((dim, dim*2))
    if return_cos:
        return gcanvas, gtheta
    else:
        return gcanvas

def read_alm(filename, return_mmax=False):
    table = pd.read_csv(filename,
                        delim_whitespace=True, header = None).values
    table = table.T;
    l = table[0].astype(np.long);
    m = table[1].astype(np.long);
    almr = table[2]; almi = table[3];
    if (m<0).any():
        raise ValueError('Negative m value encountered !')
    lmax = l.max()
    mmax = m.max()
    alm = almr*(0+0j)
    i = hp.Alm.getidx(lmax,l,m)
    alm.real[i] = almr
    alm.imag[i] = almi
    if return_mmax:
        return alm, mmax
    else:
        return alm

def radialprofile(imgdata, center=None):
    y, x = np.indices((imgdata.shape))
    if not center:
        center = np.array([(x.max()-x.min())/2.0, (x.max()-x.min())/2.0])
    r = np.sqrt((x - center[0])**2 + (y - center[1])**2)
    r = np.around(r)
    r = np.int64(r)
    tbin = np.bincount(r.ravel(), imgdata.ravel())
    nr = np.bincount(r.ravel())
    radialprofile = tbin / nr
    radialprofile = np.nan_to_num(radialprofile)
    return radialprofile

def patch2cl(image, phi_size):
    ## Fast Fourier Transform
    ak = np.fft.ifft2(image)                    # FFT scaling convention
    akshift = np.fft.fftshift(ak)               # shift low k to the center
    akabs = abs(akshift)**2.                    # take absolute square

    ## average to get Ck
    ck = radialprofile(akabs)                   # make use of radial profile
    k = np.arange(len(ck))

    ## scaling relation
    L = 2.*np.pi/(360./phi_size);
    period = np.int64(360./phi_size);
    ell = 2*np.pi/L*k
    cl = L**2.*ck

    ## set limit
    ell = ell[0:(np.int(2500/period))+1]
    cl = cl[0:(np.int(2500/period))+1]
    return ell, cl

def cart_healpix(cartview, nside):
    '''read in an matrix and return a healpix pixelization map'''
    # Generate a flat Healpix map and angular to pixels
    healpix = np.zeros(hp.nside2npix(nside), dtype=np.double)
    hptheta = np.linspace(0, np.pi, num=cartview.shape[0])[:, None]
    hpphi = np.linspace(-np.pi, np.pi, num=cartview.shape[1])
    pix = hp.ang2pix(nside, hptheta, hpphi)

    # re-pixelize
    healpix[pix] = np.fliplr(np.flipud(cartview))

    return healpix

def healpix_rotate(healpix_map, rot):
    # pix-vec
    ipix = np.arange(len(healpix_map))
    nside = np.sqrt(len(healpix_map) / 12)
    if int(nside) != nside: return print('invalid nside');
    nside = int(nside)
    vec = hp.pix2vec(int(nside), ipix)
    rot_vec = (hp.rotator.Rotator(rot=rot)).I(vec)
    irotpix = hp.vec2pix(nside, rot_vec[0], rot_vec[1], rot_vec[2])
    return np.copy(healpix_map[irotpix])

def list_rebuild(index, b):
    '''
    index: list, the list you want to rebuild
    b: list, the base list you want to take it as template to rebuid a list
    return:
    rebuild_list: list, reconstructed list based on b.
    '''
    # generate aggregate index
    length = [len(B) for B in b]
    if len(index) == np.sum(length):
        sum_length = np.insert(np.add.accumulate(length), 0, 0) # accumulate is cool
        return [index[sum_length[i]:sum_length[i+1]] for i,l in enumerate(sum_length[:-1])]
    else: print ('Not the same length!')

def ones_circle(length):
    circle = np.zeros((length, length))
    y, x = np.indices((circle.shape))
    center = np.array([(x.max() - x.min()) / 2.0, (x.max() - x.min()) / 2.0])
    r = np.sqrt((x - center[0]) ** 2 + (y - center[1]) ** 2)
    circle[r < length / 2] = 1
    return circle

def ones_number_circle(length, number):

    from PIL import Image, ImageDraw, ImageFont
    from matplotlib.image import pil_to_array

    # decide your font
    fnt = ImageFont.truetype('/Library/Fonts/Georgia.ttf', 150) # 135
    circle = ones_circle(length)
    txt = Image.new('L', size=(length, length), color=1)
    draw = ImageDraw.Draw(txt)

    w, h = draw.textsize(number, font=fnt)

    draw.text(((length - w) / 2, (length - h) / 4), text=number, font=fnt, align='center')
    txt = pil_to_array(txt)
    return circle * txt

def mask_cart(corner_x, cmbmap, phisize):
    # Healpix patch catch process
    img   = cmbmap.copy()
    NSIDE = hp.get_nside(img)
    pixtheta, pixphi = hp.pix2ang(NSIDE,
                                  np.arange(hp.nside2npix(NSIDE)))
    # rescaling rad to degree
    pixtheta *= 180 / np.pi
    pixphi   *= 180 / np.pi
    # 0 1 mask making
    img[ar(pixtheta > 90 - phisize / 2) & ar(pixtheta < 90 + phisize / 2)  &
        ar(pixphi > corner_x)           & ar(pixphi < corner_x + phisize)] = 1

    return img


import sys, os
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
print('_________________________________')
print(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
print('_________________________________')
print('_________________________________')
print(sys.path)
print('_________________________________')
import cgi
import re
import json
import project.utils as utils
import project.style as style
from docutils.core import publish_parts

from pyramid.httpexceptions import (
    HTTPFound,
    HTTPNotFound,
)

from pyramid.view import view_config

from .models import (
    DBSession,
    SolarSystem,
    Corp,
    CorpType
    )
import json

@view_config(route_name='home')
def root(request):
    return HTTPFound(location = request.route_url('view_system',
                                                  pagename='TestSolarSystem'))

@view_config(route_name='view_system', renderer='templates/main.pt')
def view_system(request):
    pagename = request.matchdict['pagename']
    page = DBSession.query(SolarSystem).filter_by(name=pagename).first()
    pagename_match = re.match('^[a-zA-Z0-9_%]+$', pagename)
    if pagename_match is None:
        return HTTPNotFound('No such Solar System in da Galaxy')
    if page is None:
        return HTTPNotFound('No such Solar System in da Galaxy')

    content = publish_parts(page.name, writer_name='html')['html_body']
    request.context = json.dumps(build_renderer_context(pagename))
    return dict(page=page, content=content)

@view_config(route_name='view_system_selected', renderer='templates/main.pt')
def view_system_selected(request):
    pagename = request.matchdict['pagename']
    corpname = request.matchdict['corpname']
    pagename_match = re.match('^[a-zA-Z0-9_%]+$', pagename)
    corpname_match = re.match('^[a-zA-Z0-9_%]+$', corpname)
    if pagename_match is None:
        return HTTPNotFound('No such Solar System in da Galaxy')
    if corpname_match is None:
        return HTTPNotFound('No such Solar System in da Galaxy')

    page = DBSession.query(SolarSystem).filter_by(name=pagename).first()
    if page is None:
        return HTTPNotFound('No such Solar System in da Galaxy')

    content = publish_parts(page.name, writer_name='html')['html_body']
    request.context = json.dumps(build_renderer_context(pagename, corpname))
    return dict(page=page, content=content)

@view_config(
    route_name='corps',
    renderer='json'
    )
def api_solar_system(request):
    response = {}
    try:
        get = request.GET
    except:
        response['error'] = 'Missing data.'
        return reponse

    if 'solarsystem' in get:
        solarsystem_match = re.match('^[a-zA-Z0-9_%]+$', get['solarsystem'])
        if solarsystem_match is None:
            response['error'] = 'Invalid characters. Only alphanumerical values + "_" are allowed.'
            return response
        else:
            solar_system_name = solarsystem_match.group(0)
    else:
        response['error'] = 'Must define a solar system name.'
        return response

    context = json.dumps(
        build_renderer_context(solar_system_name))

def build_renderer_context(solar_system_name, selected_corp_name=None):
    context = {
        'solar_system': get_solar_system_information(
            solar_system_name, selected_corp_name)
    }
    print('selected_corp_name', selected_corp_name)
    return context

def get_solar_system_information(solar_system_name, selected_corp_name):
    solar_system = DBSession.query(SolarSystem).filter_by(
        name=solar_system_name).first()
    if solar_system is None:
        context['error'] = 'Solar System do not exist. Even if the universe has no limit theoretically.'
        return context
    solar_system = utils.row2dict(solar_system)
    print('solar_system RAG', solar_system)
    corps_tests = corps = DBSession.query(Corp).all()
    corps_tests_2 = DBSession.query(CorpType).all()
    print('corps_tests_2', corps_tests_2)
    print('corps_test', corps_tests)
    corps = DBSession.query(Corp, CorpType).join(
        CorpType,
        CorpType.id == Corp.corp_type_id
    ).all()
    print('corps RAG', corps)
    corps_array = []
    for corp in corps:
        corps_array.append(get_corp_context_from_db(
            corp[0], corp[1]))

    if selected_corp_name is not None:
        solar_system['selected_corp_name'] = selected_corp_name

    solar_system['corps'] = corps_array
    print('sharing...')
    print('solar_system', solar_system)
    return solar_system

def get_corp_context_from_db(corp, corp_type):
    context = {}
    context['texture_path'] = corp.texture_path
    context['id'] = corp.id
    context['name'] = corp.name
    context['mass'] = corp.mass
    context['initial_speed'] = json.loads(corp.initial_speed)
    context['rotation_period'] = corp.rotation_period
    context['initial_position'] = json.loads(corp.initial_position)
    context['distance_friendly_crop'] = corp_type.distance_friendly_crop
    context['corp_type_id'] = corp.corp_type_id

    radius_pixel = style.radius_meter_to_pixel(
        corp.radius)
    context['radius'] = radius_pixel

    context['rotation'] = json.loads(corp.rotation)

    if corp.orbit_coordinate is not None:
        try:
            context['orbit_coordinates'] = json.loads(corp.orbit_coordinate)
        except: pass

    ring_context = {}

    if corp.ring_path is not None:
        ring_context['ring_path'] = corp.ring_path

    if corp.ring_size is not None:
        try:
            ring_context['ring_size'] = style.ring_dimension(
                radius_pixel, int(corp.ring_size))
        except: pass

    if corp.ring_rotation is not None:
        try:
            ring_context['ring_rotation'] = json.loads(
                corp.ring_rotation)
        except: pass

    if len(ring_context) == 3:
        context['ring_context'] = ring_context

    if corp.media_en is not None:
        try:
            #pass
            media = json.loads(
                corp.media_en)
            media = utils.byteify(media)
            #  context['media'] = media
        except: pass

    if corp.parent_corp_id is not None:
        try:
            context['parent_corp_id'] = corp.parent_corp_id
            print(context)
        except: pass

    atmosphere_size = style.atmosphere_thickness(
        radius_pixel, int(corp.atmosphere_size))
    context['atmosphere_context'] = {
        'color': json.loads(corp.atmosphere_color),
        'texture_path': corp.atmosphere_path,
        'size': atmosphere_size
    }
    return context


import gym
import numpy as np

from gym.spaces import Box, Discrete
from unityagents import UnityEnvironment

# keys for the environment config
EXECUTABLE = "executable"
TRAIN_MODE = "train_mode"

class BananaEnv(gym.Env):
    """An OpenAI Gym for the Banana Unity environment.
    """
    def __init__(self, env_config):
        """Create a new BananaEnv pointing at the compiled
        Unity environment.

        Args:
            env_config (dict): Environment configuration
        """
        executable = env_config[EXECUTABLE]
        train_mode = env_config[TRAIN_MODE]
        self._env = UnityEnvironment(file_name=executable)
        self._brain_name = self._env.brain_names[0]
        self._brain = self._env.brains[self._brain_name]
        self._train_mode = train_mode
        high = np.tile(np.array([np.finfo(np.float32).max,]), [37,])
        self.observation_space = Box(-high, high, dtype=np.float32)
        self.action_space = Discrete(4)

    def step(self, action):
        """Transition from one state to the next with
        the given action.

        Args:
            action (int): the action to perform

        Returns:
            tuple: next_state, reward, done, additional_info
        """
        env_info = self._env.step(int(action))[self._brain_name]
        next_state = env_info.vector_observations[0]
        reward = env_info.rewards[0]
        done = env_info.local_done[0]
        return next_state, reward, done, {}

    def reset(self):
        """Reset the Unity environment.

        Returns:
            int: the starting state
        """
        env_info = self._env.reset(train_mode=self._train_mode)[self._brain_name]
        return env_info.vector_observations[0]

    def close(self):
        """Close the Unity environment and shut down
        the process.
        """
        self._env.close()


#!/usr/bin/env python
# coding: utf-8

#
#
#

# In[1]:

import pandas as pd
import numpy as np
import matplotlib.pylab as plt
import sklearn
from sklearn.datasets import load_boston
import ipywidgets as widgets
import numpy as np
from matplotlib import cm

# # Estructura de un problema general de machine learning:
# Los modelos de aprendizaje estadístico que pueden ser industriazados,   puede ser [esquematizado](https://proceedings.neurips.cc/paper/2015/file/86df7dcfd896fcaf2674f757a2463eba-Paper.pdf) según se muestra en la siguiente imagen:
#
# ![MLOPs](https://github.com/hernansalinas/Curso_aprendizaje_estadistico/blob/main/Sesiones/imagenes/Sesion_04_MLOP_General.png?raw=true)
#
#
# ![MLOPs](https://github.com/hernansalinas/Curso_aprendizaje_estadistico/blob/main/Sesiones/imagenes/Sesion_04_MLOPS.png?raw=true)
#
#
#
# A partir de ahora, nos concentraremos en entender los modelos de que ocurren dentro de la caja negra, y como encajan dentro de una estructura general de los modelos de machine learning ML.
#
# De forma general un modelo de [ML](https://www.coursera.org/learn/machine-learning)   puede ser visualizado de la siguiente manera :
#
# ![SupervisedModel](https://github.com/hernansalinas/Curso_aprendizaje_estadistico/blob/main/Sesiones/imagenes/Sesion_04_GeneralTraining.png?raw=true)
#
#
# [Machine Learning Engineering for Production (MLOps), Course 1](https://www.coursera.org/specializations/machine-learning-engineering-for-production-mlops)
#
#
#
# ## ¿Cual es el mejor enfoque para optimizar un problema?
#
# [Non-Free-Lunch-Theorem](https://ieeexplore.ieee.org/document/585893):
# Por cada par de algoritmos hay tantos problemas en el que el primer algoritmo es mejor que el segundo como problemas en el que el segundo es mejor que el primero, como consecuencia no existe un unico algoritmo inteligente para optimizar un problema, siempre es recomendable emplear un cierto conocimiento especifico de un problema.
#
#
#
# ##  Estructura general de los problemas ML:
# 1. Construir hipotesis.
# 2. Elegir parametros
# 3. Elegir la funcion de coste.
# 4. Minimizar los datos
# 5. Validar y entrenar.
#
#
#
# ## Aprendizaje Supervisado :
#
# - Datos etiquetados
# - Retroalimentación directa
# - Predicción de resultados.
#
#
#
#
#
#
#
#
#

# En general, el modelo puede ser optmizado como sigue:
#
# Dado un conjunto de caracteristicas $X_1, X_2,X_3, ...X_n$ podemos encontrar un valor $Y$, que puede ser optimado dado el siguiente modelo#
#
# $Y = f(X) + \epsilon$
#
#
#
#

# # Regresión lineal: Para un resumen del metodo ir al final de esta sesión. Resumen del metodo
#
# Supongamos que tenemos un sistema en el que existe un predicor con  **m** valores de entrenamiento,  asi:   $ (x^{(1)}, y^{(1)} ) , (x^{(2)}, y^{(2)}) ... (x^{(m)}, y^{(m)})$
#
#
# |Training|Y         | X_1      |
# |--------|----------|----------|
# |1       |$Y^{1}$   | $X_1^{1}$|
# |2       |$Y^{2}$   | $X_1^{2}$|
# |.       |.         | .        |
# |.|.         | .        |
# |.|.         | .        |
# |m|$Y^{m}$ | $X_1^{m}$  |
#
#
#
# Podemos definir un modelo lineal  como : $h(X) = \theta_0 + \theta_1 X$ con $(\theta_0 , \theta_1)$ parámetros. Nuestro objetivo es encontrar el conjunto de puntos  $(\theta_0 , \theta_1)$ que se encuentan más "cercano" a $Y$ para cada $X$.
#
# Para la optimización, vamos a definir la función de coste **$J(\theta_1,\theta_2 )$** para las muestras de entrenamiento como aquella que garantiza la distancia euclidiana respecto a la hipotesis planteada, así:
#
# \begin{equation}
# J(\theta_1, \theta_2)=\frac{1}{2m} \sum_{i=0}^m ( h_{\theta} (x^{(i)})-y^{(i)})^2
# \end{equation}
#
# Para encontrar los valores  $(\theta_0 , \theta_1)$ se necesita  minimizar la función de coste, que permite obtener los valores más cercanos,  esta minimización podrá ser realizada a través de diferentes metodos el más conocido es el gradiente descendente.
#
# ![](https://github.com/hernansalinas/Curso_aprendizaje_estadistico/blob/main/Sesiones/imagenes/fig00.png?raw=true=50x)
#
#
#

# ### Ejemplo :
# Supongamos que tenemos lo siguientes valores, asocaidas a un determinado sistema:
#
# |Entrenamiento|Y| X_1 |
# |-|-|-|
# |0|0|0|
# |1|1|1|
# |2|2|2|
# |3|3|3|
# |4|4|4|
# |m|5|5|
#
#

# In[2]:

x = np.linspace(0, 10)
y = np.linspace(0, 10)
names_cols = ["X1","Y"]

df = pd.DataFrame({"X1":x,"Y":y})
plt.figure()
plt.plot(df.X1,df.Y,"bo")
plt.xlabel("X",fontsize=14)
plt.ylabel("Y",fontsize=14)
plt.xticks(fontsize=14)
plt.yticks(fontsize=14)
plt.show()

# Supongamos un modelo lineal para realizar la predicción, así nuestro modelo estará basado en la siguiente hipotesis de trabajo:
#
# $h(X) =\theta_0 + \theta_1 X$
#
#
# Reemplando el modelo anterior en la función de coste, tenemos que:
#
#
#
# \begin{equation}
# J(\theta_1, \theta_2)=\frac{1}{2m} \sum_{i=0}^m ( (\theta_0 + \theta_1 X)- y^{(i)})^2
# \end{equation}
#
#

# Definamos nuestro modelo y la función de coste:

# In[3]:

def H(X, θ0, θ1):
  """
  Parameter:
    theta_0 = input parameter
    theta_1 = input parameter
    X = Caracteristicas
  Return
    H value with model
  """
  return θ0 + θ1*X

def cost(X, Y, θ0, θ1):

    #m = len(Y)
    j = (H(X, θ0, θ1) - Y)**2
    J = j.mean()
    return J/(2)

# Para $\theta_0 = 0 $ y diferentes valores de $\theta_1  $, determinemos la funcion de coste y las rectas que modelan nuestro sistema, así, tenemos que:
#
#

# In[4]:

theta1 = np.linspace(-10, 12)
J = np.zeros(len(theta1))

for i,t1 in enumerate(theta1):
    J[i] = cost(df.X1.values, df.Y.values, 0, t1)
    i =i + 1

# In[5]:

@widgets.interact
def graph_values(theta_eval = np.array([-10, -5, -1, 0, 1, 5, 10])):
    plt.figure(figsize=(14, 6))
    plt.subplot(121)
    plt.plot(df.X1,df.Y,"bo")
    plt.plot(df.X1, H(df.X1, 0, theta_eval )  ,"r-", label = f"θ0 = 0, θ1 = {theta_eval}")
    plt.xticks(fontsize = 14)
    plt.yticks(fontsize = 14)
    plt.xlim( 0, 10)
    plt.ylim(-10, 10)
    plt.xlabel("X", fontsize=14)
    plt.ylabel("Y", fontsize=14)
    plt.legend()

    plt.subplot(122)
    plt.plot(theta1, J)
    plt.plot(theta_eval, cost(df.X1, df.Y,0, theta_eval), "or",label = f"θ0 = 0, θ1 = {theta_eval}")
    plt.xlabel("$\\theta_1$", fontsize=14)
    plt.ylabel("$J(\\theta_0 = 0, \\theta_1)$", fontsize=14)
    plt.xticks(fontsize = 14)
    plt.yticks(fontsize = 14)
    plt.xlim( -10, 12)
    plt.ylim( 0 , 2000)
    plt.legend()
    plt.show()

# In[6]:

# Construir la superficie para theta0, theta_1

# In[7]:

from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns

import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.ticker import LinearLocator
import numpy as np

# In[8]:

# Construyendo el grid()
N1 = 100
N2 = 100
theta0 = np.linspace(-0.5, 0.5, N1)
theta1 = np.linspace(-2.0, 3.0, N2)
t0, t1 = np.meshgrid(theta0, theta1) #Empleando un meshgrid

J = np.zeros((N1, N2)) #Evaluando los valores de la funcion de coste para el meshgrid considerado
for i,t_0 in enumerate(theta0):
  for j,t_1 in enumerate(theta1):
    J[i,j] = cost( df.X1, df.Y, t_0, t_1)
#J=J/np.max(J)

# In[9]:

fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")
ax.plot_surface(t0, t1, J,  rstride=1, cstride=1, alpha=0.5)
ax.contour(t0, t1, J, 20, lw=3, cmap="autumn_r", linestyles="solid", offset=-1)
ax.contour(t0, t1, J, 20, lw=3, colors="k", linestyles="solid")
ax.set_xlabel("$\\theta_0$")
ax.set_ylabel("$\\theta_1$")
ax.set_zlabel("J")
#ax.set_zlim(0, 30)
plt.show()

# # Otra forma de verlo

# In[10]:

X = np.linspace(0, 1, 100)
y = 0.2 + 0.2*X + 0.02*np.random.random(100)

# Datos sinteticos para entender mejor la regresion lineal

# In[11]:

plt.plot(X, y, "b.")
plt.xlabel("$x_1$", fontsize=18)
plt.ylabel("$y$", rotation=0, fontsize=18)
#plt.ylim(100, 1000)
#plt.xlim(0, 3000)
plt.show()

# \begin{equation}
# h(X) =\theta_0 + \theta_1 X
# \end{equation}
#
# \begin{equation}
# J(\theta_1, \theta_2)=\frac{1}{2m} \sum_{i=0}^m ( (\theta_0 + \theta_1 X)- y^{(i)})^2
# \end{equation}
#

# In[12]:

@widgets.interact
def graph_values(theta_eval = np.linspace(0, 0.5, 10)):
  plt.figure(figsize=(14, 6))
  plt.subplot(121)
  plt.plot(X,y,"bo")
  plt.plot(X, H(X, 0.2, theta_eval )  ,"r-", label = f"θ0 = 200, θ1 = {theta_eval}")
  plt.xticks(fontsize = 14)
  plt.yticks(fontsize = 14)
  #plt.xlim( 0, 10)
  #plt.ylim(-10, 10)
  plt.xlabel("X", fontsize=14)
  plt.ylabel("Y", fontsize=14)
  plt.legend()

# In[13]:

# Construyendo el grid()
N1 = 400
N2 = 400
theta0 = np.linspace(-1, 1, N1)  #Intercepto
theta1 = np.linspace(-0.8, 0.8, N2)   #pendiente
t0, t1 = np.meshgrid(theta0, theta1) #Empleando un meshgrid

J = np.zeros((N1, N2)) #Evaluando los valores de la funcion de coste para el meshgrid considerado

for i,t_0 in enumerate(theta0):
  for j,t_1 in enumerate(theta1):
    J[i,j] = cost( X, y, t_0, t_1)

# In[14]:

fig = plt.figure()
ax = fig.add_subplot(211, projection="3d")
ax.plot_surface(t0,t1,J,  rstride=1, cstride=1)
#ax.contour(t0, t1, J, 50,   linestyles="solid")
ax.set_xlabel("$\\theta_0$")
ax.set_ylabel("$\\theta_1$")
ax.set_zlabel("J")
#ax.set_zlim(0, 30)
plt.show()

# In[15]:

fig = plt.figure()
ax = fig.add_subplot(111)
ax.contour(t0, t1,J, 200,   linestyles="solid")
ax.set_xlabel("$\\theta_0$")
ax.set_ylabel("$\\theta_1$")
#ax.set_xlim(-0,700)
#ax.set_ylim(-0.6,0.6)
plt.show()

# En sintesis, lo que se busca es el minimo de la hypersuperficie asociada a la función de coste. Veamoslo con una superficie diferente:
#
#

# In[16]:

x1=np.linspace(-10,10,100 )
X1,X2 = np.meshgrid(x1,x1)
Y = X1**2 + X2**2

fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")
ax.plot_surface(X1, X2, Y,  cmap="autumn_r",rstride=1, cstride=1, alpha=0.5)
ax.contour(X1, X2, Y, 20, lw=3,  linestyles="solid", offset=-1)
#ax.contour(t0, t1, J, 20, lw=3, colors="k", linestyles="solid")
ax.set_xlabel("$\\theta_0$")
ax.set_ylabel("$\\theta_1$")
ax.set_zlabel("J")
#ax.set_zlim(0, 30)
plt.show()

# # Gradiente Descendente

# Sea $f(\omega) = (\omega-1)^2$ , con $\omega \in \mathbb{R} $. El minimo de esta función esta determinado por#
# \begin{equation}
# \frac{\mathrm{d}f}{\mathrm{d}\omega} = 0
# \end{equation}
#
# \begin{equation}
# \omega = 1
# \end{equation}
#
#
# Para determinar el minimo,  a través del gradiente descendente puede ser aplicada el siguiente algoritmo:
# 1. Proponer un número aleatorios inicial $\omega_i$
# 2. Para descender al mínimo de la función, encontremos un  valor para el cual
# el deriva de la función descenciende, asi:
# \begin{equation}
# \omega_{i+1} = \omega_{i} - \alpha \frac{\mathrm{d}f(\omega_i)}{\mathrm{d}\omega}
# \end{equation}
#
# donde, $\alpha$ es conocido como la tasa de aprendizaje del algoritmo.
#
# 3. Evaluar $f(\omega_{i+1})$
#
# 4. Iterar hasta encontrar el minimo de la función
#
#
# En el caso de la funcion propuesta, asumiendo un valor de $\alpha=1$, tenemos que:
#
# 1. Supongamos que $\omega_{0} = 2$, luego :
# 2. $\omega_{0+1} = 2 - 1 f'(\omega_{0}) =  = 0$
# 3. $f(0) = 1$
# 4. $\omega_{1+1} = 0 - 1 f'(\omega_{1}) = 0-1(2(0-1)) =  2$
#
# El valor del $\omega$  despues de un par de iteraciones, no es el esperado, por que no minimiza la función, se debe proponer un tasa de aprendizaje  **$\alpha$** de tal forma que permita descender suavemente para encontrar el minimo.
#

# In[17]:

f = lambda omega: (omega**2 - 1)
fp = lambda omega: 2*(omega - 1)

def gradD(omega_,alpha, fp):
  """
  Gradiente descendente
  """
  return omega_ - alpha*fp(omega_)

# In[18]:

# Determinacion del gradiente descendente
omegai = 2
alpha = 0.7
OM = []
def gradient_iteration(alpha, omegai):
  OM = []
  for i in range(0,20):
    omega = gradD(omegai, alpha , fp)
    omegai = omega
    OM.append(omegai)
    if( abs(fp(omegai))< 1E-4):
      print(f"El valor minimo es {omegai:.4f} despues de {i} iteraciones")
      break
#    print(f"omega = {omegai:.4f}\t f(omega) = {f(omegai):.4f}\t fp(omega) = {f(omegai):.4f}")

  return OM

# In[19]:

@widgets.interact
def graph_descent(OM = gradient_iteration(alpha = 0.1, omegai = 2)):
  omega = np.linspace(-10, 10, 100)
  fig = plt.figure(figsize=(10,4))
  ax = fig.add_subplot(121)
  ax.plot(omega, f(omega))
  ax.plot(OM, f(OM),"ro-", label=f" fp(omega)={f(OM):.4f}")
  plt.legend()

#
# Cuando se tiene más dimensiones, el gradiente descendente puede ser escrito como sigue:  Sea $ J(\theta_1, \theta_2) $ una función continua, nuestro objetivo es encontrar el mínimo de la función, a través del gradiente descendente, sea $\vec{\Theta} = (\theta_0, \theta_1)$
#
#
# \begin{equation}
# \Delta \vec{\Theta} =  - \alpha \nabla J(\theta_0, \theta_1)
# \end{equation}
#
# en componentes:
#
# \begin{equation}
# \Delta \theta_{0} =  - \alpha \frac{\partial J}{\partial \theta_0}
# \end{equation}
#
# \begin{equation}
# \Delta \theta_{1} =  - \alpha \frac{\partial J}{\partial \theta_1}
# \end{equation}
#
#
# La componente j-esima será:
# \begin{equation}
# \theta_{j} := \theta_{j} - \alpha \frac{\partial J}{\partial \theta_j}
# \end{equation}
#
#
#
#
#

# ¿Cómo puede encontrar el minimo el gradiente descente en 2D?. Intuición gráfica
#

# In[20]:

#Ref=https://stackoverflow.com/questions/35445424/surface-and-3d-contour-in-matplotlib
#Change the color
#Ref:https://matplotlib.org/stable/tutorials/colors/colormaps.html

X, Y = np.mgrid[-1:1:30j, -1:1:30j]
Z = np.sin(np.pi*X) * np.sin(np.pi*(Y))

fig = plt.figure(figsize=(14, 6))
ax = fig.add_subplot(121, projection="3d")
c=ax.plot_surface(X, Y, Z, cmap=cm.viridis)
ax.contour(X, Y, Z, 20, lw=3, cmap=cm.viridis, linestyles="solid", offset=-1)
#fig.colorbar(c)
#ax.contour(X, Y, Z, 10, lw=3, cmap=cm.coolwarm, colors="k", linestyles="solid")

ax2 = fig.add_subplot(122)
ax2.contour(X, Y, Z, 30, lw = 3, linestyles="-", offset=-1)
c=ax2.contourf(X, Y, Z, 30, lw=3,c="white")
fig.colorbar(c,)

#ax2.contour(X, Y, Z, 10, lw=3, colors="k", linestyles="solid")
plt.show()

# Tarea 4.1
# [Ejemplo guia: dotcsv](https://www.youtube.com/watch?v=-_A_AAxqzCg)
#
# Encontrar el minimo de la siguiente función a través del metodo del gradiente descendente https://en.wikipedia.org/wiki/Gradient_descent:
#
# \begin{equation}
# F(x,y) = \sin \left( \frac{1}{2}x^2-\frac{1}{4}y^2 +3\right) \cos (2x+1-e^y)
# \end{equation}
#
# 1. Para ello realice una gráfica de la función en 3D, y un mapa de contourno de la función.
# 2. Determine el valor mínimo de la funcion con el metodo del gradiente descendente.
#

# In[21]:

x=np.linspace(-1.5, 2.0, 200)
y=np.linspace(-1.5, 2.0, 200)
X, Y=np.meshgrid(x, y)
F = lambda x, y: (np.sin(0.5*x**2-0.25*y**2+3))*(np.cos(2*x+1-np.exp(y)))
Z = F(X,Y)

# In[22]:

fig = plt.figure(figsize=(14, 6))
ax = fig.add_subplot(121, projection="3d")
ax.plot_surface(X, Y, Z, cmap=cm.viridis, rstride=1, cstride=1, alpha=1)
ax.contour(X, Y, Z, 40, lw=3, cmap=cm.viridis, linestyles="solid", offset=-1)
ax.set_xlabel("$\\theta_0$")
ax.set_ylabel("$\\theta_1$")
ax.set_zlabel("J")
#ax.set_zlim(0, 30)

ax2 = fig.add_subplot(122)
ax2.contour(X, Y, Z, 30, lw=3, cmap=cm.viridis, linestyles="solid", offset=-1)
c=ax2.contourf(X, Y, Z, 30, lw=3,cmap=cm.viridis)
fig.colorbar(c,)

plt.show()

# # Gradiente descendente para la función de coste
#
# Proponiendo un modelo lineal, tenemos que:
# \begin{equation}
# h(X) = (\theta_0 + \theta_1 X)
# \end{equation}
#
#
#
# \begin{equation}
# J(\theta_1, \theta_2)=\frac{1}{2m} \sum_{i=1}^m ( h_{\theta} (X^{(i)})-y^{(i)})^2
# \end{equation}
#
# Asumiento una metrica  euclidiana, tenemos que:
#
# \begin{equation}
# J(\theta_1, \theta_2)=\frac{1}{2m} \sum_{i=1}^m ( (\theta_0 + \theta_1 X)- y^{(i)})^2
# \end{equation}
#
# Las derivadas de la funcion de cote vienen dadas por :
#
# \begin{equation}
# \frac{\partial J}{\partial \theta_0} = \frac{1}{m}\sum_{i=1}^{m}(h(X^{(i)})-y^{(i)})
# \end{equation}
#
# \begin{equation}
# \frac{\partial J}{\partial \theta_1} = \frac{1}{m}\sum_{i=1}^{m}(h(X^{(i)})-y^{(i)})X^{(i)}
# \end{equation}
#
#
# El gradiente descente viene dado por :
#
# \begin{equation}
# \theta_0 := \theta_0 - \alpha \frac{\partial J}{\partial \theta_0}
# \end{equation}
#
# \begin{equation}
# \theta_1 := \theta_1 - \alpha \frac{\partial J}{\partial \theta_1}
# \end{equation}
#
#

# # Modelo de *machine learning*: Solución general
#
# Un modelo general para solucionar un problema de machining learning puede ser estructurado como sigue:
#
#
# 1. Eliger el modelo a emplear:
# \begin{equation}
# h(X,\Theta)
# \end{equation}
#
# - En el caso de una regresion lineal tenemos que $h(X,\Theta=(\theta_0, \theta_1))$:
#
#     \begin{equation}
#     h(X) = (\theta_0 + \theta_1 X)
#     \end{equation}
#
#
# 2. Eligir la funcion de coste :
# -  Metrica Ecuclidiana:
# \begin{equation}
# J(\Theta)=\frac{1}{2m} \sum_{i=1}^m ( h_{\theta} (X^{(i)})-y^{(i)})^2
# \end{equation}
# - [Lista de funciones de coste que pueden ser empleadas](https://jmlb.github.io/flashcards/2018/04/21/list_cost_functions_fo_neuralnets/)
#
#
# 3. Aplicar el gradiente descendente iterativamente, hasta encontrar el minimo:
#
# \begin{equation}
# \Delta \vec{\Theta} =  - \alpha \nabla J(\Theta)
# \end{equation}
#
#
#
# - En el caso de una regresion lineal tenemos que $h(X,\Theta=(\theta_0, \theta_1))$:
#
#
# \begin{equation}
# \theta_0 := \theta_0 - \alpha \frac{\partial J}{\partial \theta_0}
# \end{equation}
#
# \begin{equation}
# \theta_1 := \theta_1 - \alpha \frac{\partial J}{\partial \theta_1}
# \end{equation}

# # Tarea 4.2:
#
# De igual forma que con los sistemas metricos, la funcion de coste puede ser generalizada para otro tipo de metricas, de forma análoga a lo que ocurre con la distancia entre dos puntos. Sabemos que la metrica euclidiana puede ser escrita como:
#
# \begin{equation}
# d = \left[ \sum_{i=1}^{D}  (x_i-x_i')^2 \right]^{1/2}  (1)
# \end{equation}
#
# donde D es la dimension del espacio, así :
#
# - Para una dimensión tenemos que D = 1:
#
# \begin{equation}
# d = |x_i-x_i'|
# \end{equation}
#
# - Para una dimensión tenemos que D = 2:
# \begin{equation}
# d = \left[ (x_1-x_1')^2 + (x_2-x_2')^2 \right]^{1/2}
# \end{equation}
#
# - Para una dimensión tenemos que D = 3:
# \begin{equation}
# d = \left[ (x_1-x_1')^2 + (x_2-x_2')^2 + (x_3-x_3')^2 \right]^{1/2}
# \end{equation}
#
#
# La metrica anterior puede ser generalizada como sigue,
#
# \begin{equation}
# d = \left[ \sum_{i=1}^{D}  (x_i-x_i')^p \right]^{1/p}
# \end{equation}
#
# Para n=2 tenemos que la metrica  de la ecuación (1).
#
#
#
# Análogo a lo anterior, la funcion de coste puede emplear cualquiera de las metricas descritas dependiendo de la dimension o caracteristicas asociadas al sistema de estudio, para datos de una sola caracteristica la función de coste puede ser escrita como sigue:
#
# \begin{equation}
# J(\theta_1, \theta_2)=\frac{1}{pm} \sum_{i=1}^m ( h_{\theta} (X^{(i)})-y^{(i)})^p
# \end{equation}
#
#
#
#
#
# Empleando los siguientes datos determine:
#
# ```
# X = np.linspace(0, 1, 100)
# y = 0.2 + 0.2*X + 0.02*np.random.random(100)
# ```
#
# Determine los valores mínimos de $\theta_0$, $\theta_1$ para los siguientes casos:
#
#
#  1. Emplea la metrica ecuclidiana con p = 2 compare su resultado con los valores empleando la libreria sklearn:[Linear Regresion](https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LinearRegression.html)
#
#  2. Emple p = 1, p = 3. ¿Como varian los resultaodos en este caso?
#  3. Construya las curvas de aprendizaje  para función del número  de datos para las situaciones descritas, ¿qué peude concluir?


"""
Author: Isabella Liu 4/28/21
Feature: Hourglass and PSMNet (stacked hourglass) module
"""

import math

from .psmnet_submodule_ray import *
from torch.autograd import Variable

class hourglass(nn.Module):
    def __init__(self, inplanes):
        super(hourglass, self).__init__()

        self.conv1 = nn.Sequential(
            convbn_3d(inplanes, inplanes * 2, kernel_size=3, stride=2, pad=1),
            nn.ReLU(inplace=True)
        )

        self.conv2 = convbn_3d(inplanes * 2, inplanes * 2, kernel_size=3, stride=1, pad=1)

        self.conv3 = nn.Sequential(
            convbn_3d(inplanes * 2, inplanes * 2, kernel_size=3, stride=2, pad=1),
            nn.ReLU(inplace=True)
        )

        self.conv4 = nn.Sequential(
            convbn_3d(inplanes * 2, inplanes * 2, kernel_size=3, stride=1, pad=1),
            nn.ReLU(inplace=True)
        )

        self.conv5 = nn.Sequential(
            nn.ConvTranspose3d(inplanes * 2, inplanes * 2, kernel_size=3, padding=1, output_padding=1, stride=2,
                               bias=False),
            nn.BatchNorm3d(inplanes * 2)
        )

        self.conv6 = nn.Sequential(
            nn.ConvTranspose3d(inplanes * 2, inplanes, kernel_size=3, padding=1, output_padding=1, stride=2,
                               bias=False),
            nn.BatchNorm3d(inplanes)
        )

    def forward(self, x, presqu, postqu):
        out = self.conv1(x)
        pre = self.conv2(out)
        if postqu is not None:
            pre = F.relu(pre + postqu, inplace=True)
        else:
            pre = F.relu(pre, inplace=True)

        out = self.conv3(pre)
        out = self.conv4(out)

        if presqu is not None:
            post = F.relu(self.conv5(out) + presqu, inplace=True)
        else:
            post = F.relu(self.conv5(out) + pre, inplace=True)

        out = self.conv6(post)
        return out, pre, post

class ConvBnReLU3D(nn.Module):
    def __init__(self, in_channels, out_channels,
                 kernel_size=3, stride=1, pad=1,
                 ):
        super(ConvBnReLU3D, self).__init__()
        self.conv = nn.Conv3d(in_channels, out_channels,
                              kernel_size, stride=stride, padding=pad, bias=False)
        self.bn = nn.ReLU(inplace=True)
        # self.bn = nn.ReLU()

    def forward(self, x):
        return self.bn(self.conv(x))

class CostRegNet(nn.Module):
    def __init__(self, in_channels):
        super(CostRegNet, self).__init__()
        self.conv0 = ConvBnReLU3D(in_channels, 8)

        self.conv1 = ConvBnReLU3D(8, 16, stride=2)
        self.conv2 = ConvBnReLU3D(16, 16)

        self.conv3 = ConvBnReLU3D(16, 32, stride=2)
        self.conv4 = ConvBnReLU3D(32, 32)

        self.conv5 = ConvBnReLU3D(32, 64, stride=2)
        self.conv6 = ConvBnReLU3D(64, 64)

        self.conv7 = nn.Sequential(
            nn.ConvTranspose3d(64, 32, 3, padding=1, output_padding=1,
                               stride=2, bias=False),
            nn.BatchNorm3d(32))

        self.conv9 = nn.Sequential(
            nn.ConvTranspose3d(32, 16, 3, padding=1, output_padding=1,
                               stride=2, bias=False),
            nn.BatchNorm3d(16))

        self.conv11 = nn.Sequential(
            nn.ConvTranspose3d(16, 8, 3, padding=1, output_padding=1,
                               stride=2, bias=False),
            nn.BatchNorm3d(8))

        # self.conv12 = nn.Conv3d(8, 8, 3, stride=1, padding=1, bias=True)

    def forward(self, x):
        conv0 = self.conv0(x)
        conv2 = self.conv2(self.conv1(conv0))
        conv4 = self.conv4(self.conv3(conv2))

        x = self.conv6(self.conv5(conv4))
        x = conv4 + self.conv7(x)
        del conv4
        x = conv2 + self.conv9(x)
        del conv2
        x = conv0 + self.conv11(x)
        del conv0
        # x = self.conv12(x)
        return x

class PSMNet(nn.Module):
    def __init__(self, maxdisp=192, transform=False):
        super(PSMNet, self).__init__()
        self.maxdisp = maxdisp
        self.transform = transform
        self.feature_extraction = FeatureExtraction(transform)

        self.down = 2
        self.cost_reg = CostRegNet(64)

        self.dres0 = nn.Sequential(
            convbn_3d(64, 32, 3, 1, 1),
            nn.ReLU(inplace=True),
            convbn_3d(32, 32, 3, 1, 1),
            nn.ReLU(inplace=True)
        )
        self.dres1 = nn.Sequential(
            convbn_3d(32, 32, 3, 1, 1),
            nn.ReLU(inplace=True),
            convbn_3d(32, 32, 3, 1, 1)
        )

        self.dres2 = hourglass(32)
        self.dres3 = hourglass(32)
        self.dres4 = hourglass(32)

        self.classif1 = nn.Sequential(
            convbn_3d(32, 32, 3, 1, 1),
            nn.ReLU(inplace=True),
            nn.Conv3d(32, 32, kernel_size=3, padding=1, stride=1, bias=False)
        )
        self.classif2 = nn.Sequential(
            convbn_3d(32, 32, 3, 1, 1),
            nn.ReLU(inplace=True),
            nn.Conv3d(32, 32, kernel_size=3, padding=1, stride=1, bias=False)
        )
        self.classif3 = nn.Sequential(
            convbn_3d(32, 32, 3, 1, 1),
            nn.ReLU(inplace=True),
            nn.Conv3d(32, 32, kernel_size=3, padding=1, stride=1, bias=False)
        )

        # for m in self.modules():
        #     if type(m) == (nn.Conv2d or nn.Conv3d or nn.Linear) :
        #         torch.nn.init.xavier_uniform_(m.weight)

        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
                m.weight.data.normal_(0, math.sqrt(2. / n))
            elif isinstance(m, nn.Conv3d):
                n = m.kernel_size[0] * m.kernel_size[1] * m.kernel_size[2] * m.out_channels
                m.weight.data.normal_(0, math.sqrt(2. / n))
            elif isinstance(m, nn.BatchNorm2d):
                m.weight.data.fill_(1)
                m.bias.data.zero_()
            elif isinstance(m, nn.BatchNorm3d):
                m.weight.data.fill_(1)
                m.bias.data.zero_()
            elif isinstance(m, nn.Linear):
                m.bias.data.zero_()

    def forward(self, img_L, img_R, img_L_transformed=None, img_R_transformed=None):

        if self.transform:
            refimg_feature = self.feature_extraction(img_L, img_L_transformed)  # [bs, 32, H/4, W/4]
            targetimg_feature = self.feature_extraction(img_R, img_R_transformed)
        else:
            refimg_feature = self.feature_extraction(img_L)  # [bs, 32, H/4, W/4]
            targetimg_feature = self.feature_extraction(img_R)

        # Cost Volume
        [bs, feature_size, H, W] = refimg_feature.size()
        cost = torch.FloatTensor(bs, feature_size * 2, self.maxdisp // 4, H, W).zero_().cuda()

        for i in range(self.maxdisp // 4):
            if i > 0:
                cost[:, :feature_size, i, :, i:] = refimg_feature[:, :, :, i:]
                cost[:, feature_size:, i, :, i:] = targetimg_feature[:, :, :, :-i]
            else:
                cost[:, :feature_size, i, :, :] = refimg_feature
                cost[:, feature_size:, i, :, :] = targetimg_feature
        cost = cost.contiguous()  # [bs, fs*2, max_disp/4, H/4, W/4]
        #print(cost.shape)

        #print(cost.shape)
        """
        cost0 = self.dres0(cost)
        cost0 = self.dres1(cost0) + cost0

        out1, pre1, post1 = self.dres2(cost0, None, None)
        #print(out1.shape, cost0.shape)
        out1 = out1 + cost0

        out2, pre2, post2 = self.dres3(out1, pre1, post1)
        out2 = out2 + cost0

        out3, pre3, post3 = self.dres4(out2, pre1, post2)
        out3 = out3 + cost0

        cost1 = self.classif1(out1)
        cost2 = self.classif2(out2) + cost1
        cost3 = self.classif3(out3) + cost2
        """
        cost = F.interpolate(cost, (self.maxdisp//4, 4 * H, 4 * W), mode='trilinear', align_corners=False)

        cost = self.cost_reg(cost)

        if self.training:
            return cost
        else:
            return cost

class Renderer(nn.Module):
    def __init__(self, D=8, W=256, input_ch=3, output_ch=1, input_ch_feat=8, skips=[4], n_rays=1024):
        """
        """
        super(Renderer, self).__init__()
        self.D = D
        self.W = W
        self.n_rays = n_rays
        self.input_ch = input_ch
        #self.input_ch_views = input_ch_views
        self.skips = skips
        #self.use_viewdirs = use_viewdirs
        self.in_ch_pts, self.in_ch_feat = input_ch, input_ch_feat

        self.pts_linears = nn.ModuleList(
            [nn.Linear(self.in_ch_pts, W, bias=True)] + [nn.Linear(W, W, bias=True) if i not in self.skips else nn.Linear(W + self.in_ch_pts, W) for i in range(D-1)])
        self.pts_bias = nn.Linear(input_ch_feat, W)
        #self.views_linears = nn.ModuleList([nn.Linear(input_ch_views + W, W//2)])

        #self.feature_linear = nn.Linear(W, W)
        self.alpha_linear = nn.Linear(W, 1)

        #self.output_linear = nn.Linear(W, output_ch)

        self.pts_linears.apply(weights_init)
        #self.views_linears.apply(weights_init)
        #self.feature_linear.apply(weights_init)
        self.alpha_linear.apply(weights_init)

        self.ray_reg = RayRegression()

        #self.rgb_linear.apply(weights_init)

    def forward(self, x, disp_candidate,B,D):

        dim = x.shape[-1]
        #in_ch_feat = dim-self.in_ch_pts
        input_pts, input_feats = torch.split(x, [self.in_ch_pts, self.in_ch_feat], dim=-1)

        h = input_pts
        bias = self.pts_bias(input_feats)
        for i, l in enumerate(self.pts_linears):
            h = self.pts_linears[i](h) * bias
            h = F.relu(h)
            if i in self.skips:
                h = torch.cat([input_pts, h], -1)

        alpha = torch.relu(self.alpha_linear(h))
        output = alpha.view(B,D,self.n_rays,1)
        pred_disp = self.ray_reg(output, disp_candidate)
        return pred_disp

def weights_init(m):
    if isinstance(m, nn.Linear):
        nn.init.kaiming_normal_(m.weight.data)
        if m.bias is not None:
            nn.init.zeros_(m.bias.data)

if __name__ == '__main__':
    # Test PSMNet
    model = PSMNet(maxdisp=192).cuda()
    model.eval()
    img_L = torch.rand(1, 3, 256, 512).cuda()
    img_R = torch.rand(1, 3, 256, 512).cuda()
    pred = model(img_L, img_R)
    print(f'pred shape {pred.shape}')   # pred shape torch.Size([1, 1, 256, 512])


#!/usr/bin/env python
# coding: utf-8

# # Filter osemosys_global datapackaged based on user-defined geographic scope

import pandas as pd
import os
import yaml
import shutil
from pathlib import Path
# from osemosys_global.configuration import ConfigFile, ConfigPaths
from configuration import ConfigFile, ConfigPaths
import logging
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO)

# CONFIGURATION PARAMETERS

config_paths = ConfigPaths()
config = ConfigFile('config')

scenario_name = config.get('scenario')
geographic_scope = config.get('geographic_scope')
remove_nodes = config.get('nodes_to_remove')

output_data_dir = config_paths.output_data_dir
scenario_dir = config_paths.scenario_dir
scenario_data_dir = config_paths.scenario_data_dir

# FILTERING

if not geographic_scope: # Check for empty list (ie. World run)
    geographic_scope = []
geographic_scope.append('INT') # 'INT' for international fuels added by default
international_fuels = ['COA', 'COG', 'GAS', 'OIL', 'PET', 'OTH', 'URN']

if not os.path.exists(scenario_data_dir):
    os.makedirs(scenario_data_dir)

for each_csv in Path(output_data_dir).glob('*.csv'):
    df = pd.read_csv(os.path.join(output_data_dir, each_csv))

    if not df.empty:
        # Do not filter if only element is international fuels
        if geographic_scope[0] != 'INT':
            if 'TECHNOLOGY' in df.columns:
                df = df.loc[df['TECHNOLOGY'].str[3:6].isin(geographic_scope) |
                            df['TECHNOLOGY'].str[6:9].isin(geographic_scope) |
                            df['TECHNOLOGY'].str[8:11].isin(geographic_scope)]

                # Filter out all international TRN techs
                df = df.loc[~(
                    df['TECHNOLOGY'].str.startswith('TRN') &
                    (~(df['TECHNOLOGY'].str[3:6].isin(geographic_scope)) |
                    ~(df['TECHNOLOGY'].str[8:11].isin(geographic_scope)))
                    )]

                if remove_nodes:
                    df = df.loc[~(df['TECHNOLOGY'].str[3:8].isin(remove_nodes) |
                                df['TECHNOLOGY'].str[6:11].isin(remove_nodes) |
                                df['TECHNOLOGY'].str[8:13].isin(remove_nodes))]

            if 'FUEL' in df.columns:
                df = df.loc[df['FUEL'].str[3:6].isin(geographic_scope) |
                            df['FUEL'].str[6:9].isin(geographic_scope) |
                            df['FUEL'].isin(international_fuels)]

                if remove_nodes:
                    df = df.loc[~(df['FUEL'].str[3:8].isin(remove_nodes) |
                                df['FUEL'].str[6:11].isin(remove_nodes))]

            if str(each_csv).split('/')[-1] == 'FUEL.csv':
                df = df.loc[df['VALUE'].str[3:6].isin(geographic_scope) |
                            df['VALUE'].str[6:9].isin(geographic_scope) |
                            df['VALUE'].isin(international_fuels)]

                if remove_nodes:
                    df = df.loc[~(df['VALUE'].str[3:8].isin(remove_nodes) |
                                df['VALUE'].str[6:11].isin(remove_nodes))]

            if str(each_csv).split('/')[-1] == 'TECHNOLOGY.csv':
                df = df.loc[df['VALUE'].str[3:6].isin(geographic_scope) |
                            df['VALUE'].str[6:9].isin(geographic_scope) |
                            df['VALUE'].str[8:11].isin(geographic_scope)]
                df = df.loc[~(
                    df['VALUE'].str.startswith('TRN') &
                    (~(df['VALUE'].str[3:6].isin(geographic_scope)) |
                    ~(df['VALUE'].str[8:11].isin(geographic_scope)))
                    )]

                if remove_nodes:
                    df = df.loc[~(df['VALUE'].str[3:8].isin(remove_nodes) |
                                df['VALUE'].str[6:11].isin(remove_nodes) |
                                df['VALUE'].str[8:13].isin(remove_nodes))]

    df.to_csv(os.path.join(os.path.join(scenario_data_dir, each_csv.name)), index = None)

logging.info('Geographic Filter Applied')


import numpy as np
import copy
from functools import reduce
import operator

rows = [[1], [1], [1], [1], [2]]
columns = [[1], [1], [1], [1], [1]]

#rows = [[1, 3], [1, 2], [4], [1], [1]]
#columns = [[4], [1, 1], [1, 1], [3], [2]]

#rows = [[3], [1], [1, 2], [2], [2, 2]]
#columns = [[2], [3], [1], [3, 1], [1, 1, 1]]

# rows = [[2, 2, 1], [2, 1, 3, 1], [2, 2], [1, 4], [2, 1, 1, 1], [5, 2], [1, 1, 3], [2, 3], [1, 1, 1, 1], [1, 3, 4]]
# columns = [[1, 3, 2], [1, 2], [1, 3, 1], [2, 2, 1, 1], [1, 2, 2], [5], [2, 1, 1, 1], [1, 1, 5], [2, 3, 1], [3, 1, 3]]

# rows = [[13],[1,1,1,3],[1,3,9],[2,5,1],[1,4,4,1],[4,3,3],[1,2,1,1],[2,1,1,1],[2,7,1],[1,3,5],[2,3,3],[1,2,5],[1,6],[9,1,2],[3,8,1]]
# columns = [[1,1,1,1,1],[2,2,3,2],[1,1,2,1,2],[1,1,2,1,1,2],[6,2,1,1],[1,2,2,3],[3,2,2,2],[1,3,1,4,2],[7,3,2],[6,2,2,1],[6,7],[1,2,2,4,1],[1,1,1,3,1],[1,2,2,3],[1,2,2,3]]

def sequence_value(seq, size):
    if len(seq) == 0:
        return 0
    if seq[0] == 1:
        return 2 ** (size - 1) + sequence_value(seq[1:], size - 2)
    else:
        seq[0] -= 1
        return 2 ** (size - 1) + sequence_value(seq, size - 1)

def sequence(bits, seq=None, new_stream=True):
    if seq is None:
        seq = []
    if len(bits) == 0:
        return seq
    elif bits[0] == '0':
        return sequence(bits[1:], seq, True)
    elif new_stream:
        seq.append(1)
    else:
        seq[-1] += 1
    return sequence(bits[1:], seq, False)

def possible_solutions(n_solution, n_actual):
    solutions = []
    n_min = n_actual if n_actual != 0 else int(bin(n_solution)[2:].rstrip('0'), 2)
    solution_seq = sequence(list(bin(n_solution)[2:]))
    for number in range(n_min, n_solution + 1):
        number_seq = sequence(list(bin(number)[2:]))
        if np.array_equal(number_seq, solution_seq) and number | n_actual == number and number not in solutions:
            solutions.append(number)
    return solutions

def cross_update(n_updated, n_to_update, index_updated, index_to_update):
    arr_updated = list(bin(n_updated)[2:].zfill(len(rows)))
    arr_to_update = list(bin(n_to_update)[2:].zfill(len(rows)))
    arr_to_update[index_to_update] = arr_updated[index_updated]
    return int(''.join(arr_to_update), 2)

rows_decimal = [sequence_value(value, len(columns)) for value in copy.deepcopy(rows)]
columns_decimal = [sequence_value(value, len(rows)) for value in copy.deepcopy(columns)]
rows_solution = [bin(value) for value in copy.deepcopy(rows_decimal)]
columns_solution = [bin(value) for value in copy.deepcopy(columns_decimal)]

def is_solved(solution):
    for i in range(len(solution[0])):
        if bin(solution[0][i]).count('1') != rows_solution[i].count('1'):
            return False
        if bin(solution[1][i]).count('1') != columns_solution[i].count('1'):
            return False
    return True

def print_solution(solution):
    biggest_row = len(max(rows, key=len))
    biggest_col = len(max(columns, key=len))
    for i in range(biggest_col, 0, -1):
        row = '   ' * biggest_row + '\t  '
        for j in range(len(columns)):
            row += (str(columns[j][len(columns[j]) - i]) if len(columns[j]) >= i else " ") + "   "
        print(row)
    for i in range(len(solution[0])):
        row_sequence = [str(element) for element in rows[i]]
        row_sequence = [' '] * (biggest_row - len(row_sequence)) + row_sequence
        row = '   '.join(str(element) for element in row_sequence)
        row += "\t| "
        binary_row = list(bin(solution[0][i])[2:].zfill(len(rows)))
        for j in range(len(binary_row)):
            row += ("X" if binary_row[j] == '1' else " ") + " | "
        print(row)

def solve(solution = np.zeros((2, len(rows))).astype(int), last_solution = None):

    for i in range(len(columns)):
        possibilities = possible_solutions(columns_decimal[i], solution[1][i])
        if len(possibilities) == 0:
            return False
        solution[1][i] = reduce(operator.and_,possibilities)
        for j in range(len(rows)):
            solution[0][j] = cross_update(solution[1][i], solution[0][j], j, i)

    for i in range(len(rows)):
        possibilities = possible_solutions(rows_decimal[i], solution[0][i])
        if len(possibilities) == 0:
            return False
        solution[0][i] = reduce(operator.and_,possibilities)
        for j in range(len(columns)):
            solution[1][j] = cross_update(solution[0][i], solution[1][j], j, i)

    if is_solved(solution):
        return solution

    if np.all(solution == last_solution):
        for i in range(len(columns)):
            possibilities = possible_solutions(columns_decimal[i], solution[1][i])
            if len(possibilities) > 1:
                for p in range(len(possibilities)):
                    solution[1][i] = possibilities[p]
                    new_solution = solve(copy.deepcopy(solution), copy.deepcopy(solution))
                    if np.any(new_solution):
                        return new_solution
        return False

    return solve(solution, copy.deepcopy(solution))

solution = solve()
if np.any(solution):
    print_solution(solution)
else:
    print("Unsolvable")


from flask import Flask, render_template, request, redirect, url_for, flash
from flask_mysqldb import MySQL

app = Flask(__name__)
app.secret_key = 'ajay'

app.config['MYSQL_HOST'] = 'localhost'
app.config['MYSQL_USER'] = 'root'
app.config['MYSQL_PASSWORD'] = 'rootroot0305'
app.config['MYSQL_DB'] = 'crud'

mysql = MySQL(app)

@app.route('/')
def Index():
    cur = mysql.connection.cursor()
    cur.execute("SELECT  * FROM students")
    data = cur.fetchall()
    return render_template('index2.html', students=data )

@app.route('/course')
def course():
    cur = mysql.connection.cursor()
    cur.execute("SELECT * FROM course")
    data = cur.fetchall()
    return render_template('course.html', course=data)

@app.route('/college')
def college():
    cur = mysql.connection.cursor()
    cur.execute("SELECT * FROM college")
    data = cur.fetchall()
    return render_template('college.html', college=data)

# @app.route('/index')
# def index():
#     return render_template('index2.html')

@app.route('/insert', methods = ['POST'])
def insert():

    if request.method == "POST":
        flash("Data Inserted Successfully")
        ID = request.form['id']
        Firstname = request.form['firstname']
        Lastname = request.form['lastname']
        Course = request.form['course']
        Year = request.form['year']
        Gender = request.form['gender']
        cur = mysql.connection.cursor()
        cur.execute("INSERT students (Id, firstname, lastname, course, year, gender) VALUES (%s, %s, %s, %s, %s,%s)", (ID, Firstname, Lastname, Course, Year, Gender ))
        mysql.connection.commit()
        return redirect(url_for('Index'))

@app.route('/insert_course', methods = ['POST'])
def insert_course():

    if request.method == 'POST':
        flash("Data Inserted Successfully")
        code = request.form['code']
        name = request.form['name']
        college = request.form['college']
        cur = mysql.connection.cursor()
        cur.execute("INSERT INTO course (code, name, college) VALUES (%s, %s, %s)", (code, name, college))
        mysql.connection.commit()
        return redirect(url_for('course'))

@app.route('/insert_college', methods = ['POST'])
def insert_college():

    if request.method == 'POST':
        flash("Data Inserted Successfully")
        code = request.form['code']
        name = request.form['name']
        cur = mysql.connection.cursor()
        cur.execute("INSERT INTO course (code, name) VALUES (%s, %s)", (code, name))
        mysql.connection.commit()
        return redirect(url_for('college'))

@app.route('/delete/<string:id_data>', methods = ['GET'])
def delete(id_data):
    flash("Record Has Been Deleted Successfully")
    cur = mysql.connection.cursor()
    cur.execute("DELETE FROM students WHERE id=%s", (id_data,))
    mysql.connection.commit()
    return redirect(url_for('Index'))

@app.route('/delete_course', methods = ['GET'])
def delete_course(id_course_data):
    flash("Record Has Been Deleted Successfully")
    cur = mysql.connection.cursor()
    cur.execute("DELETE FROM course WHERE id_course=%s", (id_course_data,))
    mysql.connection.commit()
    return redirect(url_for('course'))

@app.route('/delete_college/<string:id_college_data>', methods = ['GET'])
def delete_college(id_college_data):
    flash("Record Has Been Deleted Successfully")
    cur = mysql.connection.cursor()
    cur.execute("DELETE FROM college WHERE id_college=%s", (id_college_data))
    mysql.connection.commit()
    return redirect(url_for('college'))

@app.route('/update',methods=['POST','GET'])
def update():

    if request.method == 'POST':
        ID = request.form['id']
        Firstname = request.form['firstname']
        Lastname = request.form['lastname']
        Course = request.form['course']
        Year = request.form['year']
        Gender = request.form['gender']
        cur = mysql.connection.cursor()
        flash("Data Updated Successfully")
        cur.execute("""
               UPDATE students
               SET firstname=%s,
                Lastname=%s,
                 Course=%s,
                 Year=%s,
                 gender=%s
               WHERE id=%s
            """, (Firstname, Lastname, Course, Year,  Gender, ID ))
        mysql.connection.commit()
        return redirect(url_for('Index'))

@app.route('/update_course',  methods=['POST', 'GET'])
def update_course(id_course):

    if request.method == 'POST':
        Id_course = request.form['id_course']
        code = request.form['code']
        name = request.form['name']
        college = request.form['college']
        cur = mysql.connection.cursor()
        flash("Data Updated Successfully")

        cur.execute("""
               UPDATE course
               SET code=%s,
                name=%s,
                 college=%s,
               WHERE id_course=%s
            """, (code, name, college, id_course))
        mysql.connection.commit()
        return redirect(url_for('Course'))

@app.route('/update_college',methods=['POST', 'GET'])
def update_college(id_college):

    if request.method == 'POST':
        id = request.form['id_college']
        code = request.form['code']
        name = request.form['name']
        cur = mysql.connection.cursor()
        flash("Data Updated Successfully")

        cur.execute("""
               UPDATE college
               SET code=%s,
                name=%s,
               WHERE id_college=%s
            """, (code, name, id_college))
        mysql.connection.commit()
        return redirect(url_for('College'))

if __name__ == "__main__":
    app.run(debug=True)


from py2neo import Graph, Node

import pandas as pd

class CarGraph:
    def __init__(self):
        self.datapath = 'all_cars.csv'
        self.g = Graph("http://localhost:7474", auth=("neo4j", "hulongxing"))    #连接

    def read_nodes(self):
        data = pd.read_csv(self.datapath)   #读取数据

        brand_names = []
        serise_names = []
        scores = []
        levels = []
        structs = []
        EngineORbatterys = []
        SpeedchunkORcharges = []
        types = []
        prices = []
        status = []
        urls =  []

        cars_info = []

        #构建关系
        rel_brand2serise = []   #品牌与车系关系(1:n)
        rel_brand2price = []    #品牌与价格关系(1:n)
        rel_brand2type = []     #品牌与类型关系(1:n)
        rel_brand2urls = []     #品牌与链接关系(1:n)

        rel_name2scores = []    #车系评分关系(1:1)
        rel_serise2levels = []  #车系与车型(轿车、SUV等)关系(1:1)
        rel_serise2struct = []  #车系与车架关系(1:1)
        rel_serise2Engine = []  #车系与引擎关系(OR续航时间)(1:1)
        rel_serise2Chunk = []   #车系与变速器(OR充电时间)关系(1:1)
        rel_serise2type = []    #车系与类型关系(是否为新能源汽车)(1:1)
        rel_serise2price = []   #车系与价格(1:1)
        rel_serise2status = []  #车系与销售状态关系(1:1)
        rel_serise2url = []     #车系链接(1:1)

        current_serise_names = []
        current_prices = []
        current_types = []
        current_urls = []

        current_serise_name = []
        current_price = []
        current_type = []
        current_url = []

        current_brand = data.iloc[0, :]['brand_name']
        for i in range(data.shape[0]):
            car_dict = {}

            car_dict['brand_name'] = data.iloc[i, :]['brand_name']
            car_dict['serise_name'] = data.iloc[i, :]['serise_name']
            car_dict['score'] = data.iloc[i, :]['score']
            car_dict['level'] = data.iloc[i, :]['level']
            car_dict['struct'] = data.iloc[i, :]['struct']
            car_dict['EngineORbattery'] = data.iloc[i, :]['EngineORbattery']
            car_dict['SpeedchunkORcharge'] = data.iloc[i, :]['SpeedchunkORcharge']
            car_dict['type'] = data.iloc[i, :]['type']
            car_dict['price'] = data.iloc[i, :]['price']
            car_dict['status'] = data.iloc[i, :]['status']
            car_dict['url'] = data.iloc[i, :]['url']

            brand_names.append(car_dict['brand_name'])
            serise_names.append(car_dict['serise_name'])
            scores.append(car_dict['score'])
            levels.append(car_dict['level'])
            structs.append(car_dict['struct'])
            EngineORbatterys.append(car_dict['EngineORbattery'])
            SpeedchunkORcharges.append(car_dict['SpeedchunkORcharge'])
            types.append(car_dict['type'])
            prices.append(car_dict['price'])
            status.append(car_dict['status'])
            urls.append(car_dict['url'])

            if current_brand == car_dict['brand_name']:
                #如果之前已经记录了品牌，则
                current_serise_name.append(car_dict['serise_name'])
                current_price.append(car_dict['price'])
                current_type.append(car_dict['type'])
                current_url.append(car_dict['url'])

            else:
                current_serise_names.append(current_serise_name)
                current_prices.append(current_price)
                current_types.append(current_type)
                current_urls.append(current_url)
                current_serise_name, current_price, current_type, current_url = [], [], [], []  #清空操作，为下次做准备
                #如果之前已经记录了品牌，则
                current_serise_name.append(car_dict['serise_name'])
                current_price.append(car_dict['price'])
                current_type.append(car_dict['type'])
                current_url.append(car_dict['url'])
                current_brand = car_dict['brand_name']

            if i == data.shape[0]-1:
                current_serise_names.append(current_serise_name)
                current_prices.append(current_price)
                current_types.append(current_type)
                current_urls.append(current_url)

            rel_name2scores.append([car_dict['serise_name'], car_dict['score']])#车系评分关系(1:1)
            rel_serise2levels.append([car_dict['serise_name'], car_dict['level']])#车系与车型(轿车、SUV等)关系(1:1)
            rel_serise2struct.append([car_dict['serise_name'], car_dict['struct']]) #车系与车架关系(1:1)
            rel_serise2Engine.append([car_dict['serise_name'], car_dict['EngineORbattery']])  #车系与引擎关系(OR续航时间)(1:1)
            rel_serise2Chunk.append([car_dict['serise_name'], car_dict['SpeedchunkORcharge']])  #车系与变速器(OR充电时间)关系(1:1)
            rel_serise2type.append([car_dict['serise_name'], car_dict['type']])  #车系与类型关系(是否为新能源汽车)(1:1)
            rel_serise2price.append([car_dict['serise_name'], car_dict['price']])  #车系与价格(1:1)
            rel_serise2status.append([car_dict['serise_name'], car_dict['status']]) #车系与销售状态关系(1:1)
            rel_serise2url.append([car_dict['serise_name'], car_dict['url']])     #车系链接(1:1)

            cars_info.append(car_dict)
        #添加关系
        convert = list(set(brand_names))
        convert.sort(key=brand_names.index)

        rel_brand2serise = list(zip(convert, current_serise_names))

        rel_brand2price  = list(zip(convert, current_prices))
        rel_brand2type   = list(zip(convert, current_types))
        rel_brand2urls   = list(zip(convert, current_urls))

        return set(brand_names),serise_names,set(scores),set(levels),set(structs) ,set(EngineORbatterys),set(SpeedchunkORcharges),set(types),\
        set(prices),set(status),urls,rel_brand2serise, rel_brand2price, rel_brand2type, rel_brand2urls, rel_name2scores,\
        rel_serise2levels, rel_serise2struct, rel_serise2Engine, rel_serise2Chunk,\
        rel_serise2type, rel_serise2price, rel_serise2status, rel_serise2url, cars_info

        #额外处理一对多的列表

    def create_node(self, label, nodes):
        count = 0
        for node_name in nodes:
            node = Node(label, name=node_name)  #py2neo方法，node_name就是一类节点中的某一个，要建立对象
            self.g.create(node)
            count += 1
            print(count, len(nodes))
        return

    def create_car_nodes(self, cars_info):
        count = 0
        for car_dict in cars_info:
            node = Node("Car", name=car_dict['serise_name'], brand_name=car_dict['brand_name'],
                        score=car_dict['score'] ,level=car_dict['level'],
                        struct=car_dict['struct'],EngineORbattery=car_dict['EngineORbattery'],
                        SpeedchunkORcharge=car_dict['SpeedchunkORcharge'],
                        type=car_dict['type'] , price=car_dict['price'],
                        status=car_dict['status'], url=car_dict['url']
                        )
            self.g.create(node)
            count += 1
            print(count)        #打印每一个品牌的
        return

    '''创建知识图谱实体节点类型schema'''
    def create_graphnodes(self):
        brand_names,serise_names,scores,levels,structs ,EngineORbatterys,SpeedchunkORcharges,types,\
        prices,status,urls,rel_brand2serise, rel_brand2price, rel_brand2type, rel_brand2urls, rel_name2scores,\
        rel_serise2levels, rel_serise2struct, rel_serise2Engine, rel_serise2Chunk,\
        rel_serise2type, rel_serise2price, rel_serise2status, rel_serise2url, cars_info = self.read_nodes()

        self.create_car_nodes(cars_info)
        self.create_node('brand_names', brand_names)
        print(len(brand_names))
        self.create_node('serise_names', serise_names)
        print(len(serise_names))
        self.create_node('scores', scores)
        print(len(scores))
        self.create_node('levels', levels)
        print(len(levels))
        self.create_node('structs', structs)
        print(len(structs))
        self.create_node('EngineORbatterys', EngineORbatterys)
        print(len(EngineORbatterys))
        self.create_node('SpeedchunkORcharges', SpeedchunkORcharges)
        print(len(SpeedchunkORcharges))
        self.create_node('types', types)
        print(len(types))
        self.create_node('prices', prices)
        print(len(prices))
        self.create_node('status', status)
        print(len(status))
        self.create_node('urls', urls)
        print(len(urls))
        return

    def create_relationship(self, start_node, end_node, edges, rel_type, rel_name):
        count = 0
        #                                 # 去重处理  传进来的edges是列表，列表的元素还是列表
        # set_edges = []
        # for edge in edges:              #遍历每一个元素，把###加入进去，之后再分割
        #     set_edges.append('###'.join(edge))
        # all = len(set(set_edges))
        #先判断edge类型，如果是列表，则for循环，如果是字符串则
        for edge in edges:
            p  = edge[0]
            qs = edge[1]
            print(str(type(qs)))

            if str(type(qs)) == "<class 'str'>":
                query = "match(p:%s),(q:%s) where p.name='%s'and q.name='%s' create (p)-[rel:%s{name:'%s'}]->(q)" % (
                start_node, end_node, p, qs, rel_type, rel_name) #关键代码关键代码关键代码！！！创建指定两点之间指定的关系
                try:
                    self.g.run(query)   #pyneo方法
                    print("{}---{}---->{}".format(p, rel_type, qs))
                    count += 1
                    print(rel_type, count, all)
                except Exception as e:
                    print(e)
            else:
                for q in qs:
                    query = "match(p:%s),(q:%s) where p.name='%s'and q.name='%s' create (p)-[rel:%s{name:'%s'}]->(q)" % (
                    start_node, end_node, p, q, rel_type, rel_name) #关键代码关键代码关键代码！！！创建指定两点之间指定的关系

                    try:
                        self.g.run(query)   #pyneo方法
                        print("{}---{}---->{}".format(p, rel_type, q))
                        count += 1
                        print(rel_type, count, all)
                    except Exception as e:
                        print(e)

        # for edge in set(set_edges):     #去重主要是使用了set，set中的元素各不相同。
        #     edge = edge.split('###')    #还原
        #     p = edge[0]                 #p是起始点，如百日咳
        #     q = edge[1]                 #q是终点，  如清蒸鸡蛋羹
        #     query = "match(p:%s),(q:%s) where p.name='%s'and q.name='%s' create (p)-[rel:%s{name:'%s'}]->(q)" % (
        #         start_node, end_node, p, q, rel_type, rel_name) #关键代码关键代码关键代码！！！创建指定两点之间指定的关系

        #     try:
        #         self.g.run(query)   #pyneo方法
        #         count += 1
        #         print(rel_type, count, all)
        #     except Exception as e:
        #         print(e)
        return

    def create_graphrels(self):
        brand_names,serise_names,scores,levels,structs ,EngineORbatterys,SpeedchunkORcharges,types,\
        prices,status,urls,rel_brand2serise, rel_brand2price, rel_brand2type, rel_brand2urls, rel_name2scores,\
        rel_serise2levels, rel_serise2struct, rel_serise2Engine, rel_serise2Chunk,\
        rel_serise2type, rel_serise2price, rel_serise2status, rel_serise2url, cars_info = self.read_nodes()

        self.create_relationship('brand_names', 'Car', rel_brand2serise, 'has_serises', '拥有车系')
        # self.create_relationship('brand_names', 'prices', rel_brand2price, 'brand_price', '汽车价格')
        # self.create_relationship('brand_names', 'types', rel_brand2type, 'brand_types', '类型')
        # self.create_relationship('brand_names', 'urls', rel_brand2urls, 'brand_url', '链接')

        self.create_relationship('Car', 'scores', rel_name2scores, 'serise_score', '评分')
        self.create_relationship('Car', 'levels', rel_serise2levels, 'serise_level', '级别')
        self.create_relationship('Car', 'structs', rel_serise2struct, 'serise_struct', '架构')
        self.create_relationship('Car', 'EngineORbatterys', rel_serise2Engine, 'serise_engine', '引擎OR续航')
        self.create_relationship('Car', 'SpeedchunkORcharges', rel_serise2Chunk, 'serise_chunk', '变速箱OR充电')
        self.create_relationship('Car', 'types', rel_serise2type, 'serise_type', '类型')
        self.create_relationship('Car', 'status', rel_serise2status, 'serise_status', '销售状态')
        self.create_relationship('Car', 'urls', rel_serise2url, 'serise_urls', '链接')

    def export_data(self):
        brand_names,serise_names,scores,levels,structs ,EngineORbatterys,SpeedchunkORcharges,types,\
        prices,status,urls,rel_brand2serise, rel_brand2price, rel_brand2type, rel_brand2urls, rel_name2scores,\
        rel_serise2levels, rel_serise2struct, rel_serise2Engine, rel_serise2Chunk,\
        rel_serise2type, rel_serise2price, rel_serise2status, rel_serise2url, cars_info = self.read_nodes()

        f_brand_names           = open('brand_names.txt', 'w+', encoding='utf-8')
        f_serise_names          = open('serise_names.txt', 'w+', encoding='utf-8')
        f_scores                = open('scores.txt', 'w+', encoding='utf-8')
        f_levels                = open('levels.txt', 'w+', encoding='utf-8')
        f_structs               = open('structs.txt', 'w+', encoding='utf-8')
        f_EngineORbatterys      = open('EngineORbatterys.txt', 'w+', encoding='utf-8')
        f_SpeedchunkORcharges   = open('SpeedchunkORcharges.txt', 'w+', encoding='utf-8')
        f_types                 = open('types.txt', 'w+', encoding='utf-8')
        f_prices                = open('prices.txt', 'w+', encoding='utf-8')
        f_status                = open('status.txt', 'w+', encoding='utf-8')
        f_urls                  = open('urls.txt', 'w+', encoding='utf-8')

        f_brand_names.write('\n'.join(list(brand_names)))
        f_serise_names.write('\n'.join(list(serise_names)))
        f_scores.write('\n'.join(list(scores)))
        f_levels.write('\n'.join(list(levels)))
        print(structs)
        f_structs.write('\n'.join(list(structs)))
        f_EngineORbatterys.write('\n'.join(list(EngineORbatterys)))
        f_SpeedchunkORcharges.write('\n'.join(list(EngineORbatterys)))
        f_types.write('\n'.join(list(types)))
        f_prices.write('\n'.join(list(prices)))
        f_status.write('\n'.join(list(status)))
        f_urls.write('\n'.join(list(urls)))

        f_brand_names.close()
        f_serise_names.close()
        f_scores.close()
        f_levels.close()
        f_structs.close()
        f_EngineORbatterys.close()
        f_types.close()
        f_prices.close()
        f_status.close()
        f_urls.close()
        return

if __name__ == '__main__':
    handler = CarGraph()
    handler.export_data()
    handler.create_graphnodes()
    handler.create_graphrels()