import operator
from queue import PriorityQueue

import numpy as np
import torch

from params import JOIN_TOKEN

def greedy_decode(vocab, decoder, with_attention, trg_indexes, hidden, cell, enc_output, eos_token, device,
                  max_len=100):
    """

    Args:
        vocab: vocabulary
        decoder: decoder model
        with_attention: bool value marking if decoder is with attention or not
        trg_indexes: target indexes
        hidden: hidden state
        cell: cell state
        enc_output: encoder output
        eos_token: index of the EOS token
        device: device
        max_len: max length of the generated sequence

    Returns: a generated sequence

    """
    trg = []
    enc_output = enc_output.permute(1, 0, 2)
    decoder_h = (hidden, cell)
    for i in range(max_len):
        trg_tensor = torch.LongTensor([trg_indexes[-1]]).to(device)
        if with_attention:
            predicted, decoder_h, attn_weights = decoder(trg_tensor, decoder_h, enc_output)
        else:
            predicted, hidden, cell = decoder(trg_tensor, hidden, cell)
        pred_token = predicted.argmax(1).item()
        trg.append(pred_token)
        trg_indexes = torch.cat((trg_indexes, torch.LongTensor([pred_token]).to(device)), 0)
        if pred_token == eos_token:
            break
    return JOIN_TOKEN.join([vocab.itos[i] for i in trg])

class BeamSearchNode(object):
    def __init__(self, hidden_state, previous_node, word_ids, log_prob, length):
        '''
        :param hiddenstate: hidden state
        :param previousNode: previous BeamSearchNode
        :param wordId: indexes of the generated words
        :param logProb: log probabilities of the generated words
        :param length: length of the generated sequence
        '''
        self.h = hidden_state
        self.prev_node = previous_node
        self.word_ids = word_ids
        self.logp = log_prob
        self.length = length

    def eval(self, alpha=1.0):
        reward = np.random.uniform(0.1, 10 ** (-20))
        # Add here a function for shaping a reward
        return self.logp / float(self.length - 1 + 1e-6) + alpha * reward

def beam_decode_mixed(vocab, beam_width, max_len, max_sentence_count, models, with_attention, lm_weights,
                      stylized_score_tensors, target_tensor, encoder_hiddens, encoder_outputs, sos_token, eos_token,
                      device):
    """
    Args:
        vocab: vocabulary
        beam_width: width of the beam
        max_len: max length of a generated sequence
        max_sentence_count: max sentence counter
        models: array of the models for sequence generation
        with_attention: a bool value, which indicated if the decoder is with attention or not
        lm_weights: stylized weights for the language models
        stylized_score_tensors: tensors for stylized generation
        target_tensor: target tensor
        encoder_hiddens: last hidden state from the encoder
        encoder_outputs: encoder outputs
        sos_token: index of the SOS token
        eos_token: index of the EOS token
        device: device

    Returns: generated sequences

    """
    # can be 1 or several decoders
    if len(models) != 1:
        assert len(models) == len(lm_weights), "Number of model decoders should be equal to the number of scores"
    else:
        assert len(lm_weights) - 1 == len(
            stylized_score_tensors), "Number of styles should be equal to number of discrete distribution "

    assert sum(lm_weights) == 1, "Sum of scores must be 1"

    decoded_batch = []
    max_len = max(max_len, 2)
    beam_width = max(beam_width, 2)
    sentences = ""
    for idx in range(target_tensor.size(0)):
        if isinstance(encoder_hiddens, tuple):
            decoder_hidden = (encoder_hiddens[0][:, idx, :].unsqueeze(1), encoder_hiddens[1][:, idx, :].unsqueeze(1))
        else:
            decoder_hidden = encoder_hiddens[:, idx, :].unsqueeze(1)
        encoder_output = encoder_outputs[:, idx, :].unsqueeze(1)

        decoder_input = torch.LongTensor([sos_token]).to(device)
        node = BeamSearchNode(decoder_hidden, None, decoder_input, 0, 1)
        nodes_queue = PriorityQueue()
        nodes_queue.put((-node.eval(), node))
        end_nodes = []
        while not nodes_queue.empty():
            score, n = nodes_queue.get()
            if len(end_nodes) >= max_sentence_count:
                break
            if n.length >= max_len or n.word_ids[-1] == eos_token:
                end_nodes.append((score, n))
                continue
            decoder_input = torch.LongTensor([n.word_ids[-1]]).to(device)
            decoder_hidden = n.h
            decoder_hs = []
            for i in range(len(models)):
                if models[i].__class__.__name__ == "Seq2Seq" and with_attention:
                    decoder_output, (decoder_h, cell), _ = models[i].decoder(decoder_input, decoder_hidden,
                                                                             encoder_output)
                else:
                    decoder_output, decoder_h, cell = models[i].decoder(decoder_input, decoder_hidden[0],
                                                                        decoder_hidden[1])
                decoder_hs.append((decoder_h, cell))
                if i == 0:
                    decoder_outputs = decoder_output
                else:
                    decoder_outputs = torch.cat((decoder_outputs, decoder_output), 0)
            if len(models) == 1:
                dec_output = decoder_outputs
                for i in range(len(stylized_score_tensors)):
                    stylized_out = []
                    # stylized_score_tensors is a list of tensors of size [len(vocab)]
                    for coef in range(stylized_score_tensors[i].size(0)):
                        stylized_out.append(torch.exp(stylized_score_tensors[i][coef]) + dec_output[0][coef])
                    stylized_out = torch.FloatTensor(stylized_out).to(device).unsqueeze(0)
                    decoder_outputs = torch.cat((decoder_outputs, stylized_out), 0)
            decoder_outputs = decoder_outputs.transpose(0, 1)

            score = torch.FloatTensor(lm_weights).to(device).unsqueeze(1)
            decoder_out_scored = torch.matmul(decoder_outputs, score).transpose(0, 1)
            for i in range(len(decoder_hs)):
                hidden = decoder_hs[i][0]
                cell = decoder_hs[i][1]
                if i == 0:
                    new_hidden = hidden * lm_weights[i]
                    new_cell = cell * lm_weights[i]
                else:
                    new_hidden = torch.add(new_hidden, hidden * lm_weights[i])
                    new_cell = torch.add(new_cell, cell * lm_weights[i])

            log_prob, indexes = torch.topk(decoder_out_scored, beam_width)
            decoder_hidden = (new_hidden, new_cell)
            for new_k in range(beam_width):
                decoded_t = indexes[0][new_k].unsqueeze(0)
                log_p = log_prob[0][new_k].item()
                decoded_t = torch.cat((n.word_ids, decoded_t), 0)
                node = BeamSearchNode(decoder_hidden, n, decoded_t, n.logp + log_p, n.length + 1)
                score = -node.eval()
                nodes_queue.put((score, node))

        for score, n in sorted(end_nodes, key=operator.itemgetter(0)):
            utterance = n.word_ids
            sentence = [vocab.itos[i] for i in utterance]
            sentences += JOIN_TOKEN.join(sentence) + "<eos>"
            decoded_batch.append(sentence)
    return sentences

def beam_decode(vocab, beam_width, max_len, topk, decoder, with_attention, target_tensor, decoder_hiddens,
                encoder_output, sos_token, eos_token, device):
    '''
    :param target_tensor: target indexes tensor of shape [B, T] where B is the batch size and T is the maximum length of the output sentence
    :param decoder_hidden: input tensor of shape [1, B, H] for start of the decoding
    :param encoder_outputs: if you are using attention mechanism you can pass encoder outputs, [T, B, H] where T is the maximum length of input sentence
    :return: decoded_batch
    '''
    decoded_batch = []
    sentences = ""

    # decoding goes sentence by sentence
    for idx in range(target_tensor.size(0)):
        decoder_hidden = decoder_hiddens

        # Start with the start of the sentence token
        decoder_input = torch.LongTensor([sos_token]).to(device)
        # Number of sentence to generate
        endnodes = []
        number_required = max(topk, 1)

        # starting node -  hidden vector, previous node, word id, logp, length
        node = BeamSearchNode(decoder_hidden, None, decoder_input, 0, 1)
        nodes = PriorityQueue()

        # start the queue
        nodes.put((-node.eval(), node))

        # start beam search
        while True:
            # fetch the best node
            score, n = nodes.get()
            decoder_input = n.word_ids
            decoder_hidden = n.h

            if (n.word_ids.item() == eos_token and n.prev_node != None) or n.length >= max_len:
                endnodes.append((score, n))
                # if we reached maximum of sentences required
                if len(endnodes) >= number_required:
                    break
                else:
                    continue

            if with_attention:
                decoder_output, decoder_hidden, attn_weights = decoder(decoder_input, decoder_hidden, encoder_output)
            else:
                # decode for one step using decoder
                decoder_output, decoder_h, cell = decoder(decoder_input, decoder_hidden[0], decoder_hidden[1])
                decoder_hidden = (decoder_h, cell)
            # PUT HERE REAL BEAM SEARCH OF TOP
            log_prob, indexes = torch.topk(decoder_output, beam_width)
            nextnodes = []

            for new_k in range(beam_width):
                decoded_t = indexes[0][new_k].unsqueeze(0)
                log_p = log_prob[0][new_k].item()
                node = BeamSearchNode(decoder_hidden, n, decoded_t, n.logp + log_p, n.length + 1)
                score = -node.eval()
                nextnodes.append((score, node))

            # put them into queue
            for i in range(len(nextnodes)):
                score, nn = nextnodes[i]
                nodes.put((score, nn))

        # choose nbest paths, back trace them
        if len(endnodes) == 0:
            endnodes = [nodes.get() for _ in range(topk)]

        for score, n in sorted(endnodes, key=operator.itemgetter(0)):
            utterance = [n.word_ids]
            # back trace
            while n.prev_node is not None:
                n = n.prev_node
                utterance.append(n.word_ids)
            utterance = utterance[::-1]
            sentence = [vocab.itos[i] for i in utterance]
            sentences += ' '.join(sentence) + " <eos>\n"
            decoded_batch.append(sentence)

    return sentences


import numpy as np, random, math, time
from scipy.stats import norm
import matplotlib

class Settings():
    def __init__(self):
        self.min_dist = 100
        self.initial_num = 150
        self.tick_time = 0 #time between updates during run mode

        #Max initial energy is 723
        self.efficiency_stdev = 10 #50 is less selective, 10 is very selective, 20 is less shocking
        self.area_energy_cost = 30
        self.height_growth_cost = 300
        self.area_growth_cost = 20
        self.seed_cost = 100

        #mutation_rate out of 1
        self.mutation_rate = 0.3

        self.max_age = 20
        self.leaf_obscurity = 0.8

        self.x_size = 300
        self.y_size = 300

        #initial light grids
        self.understory_light = np.full((self.x_size,self.y_size), 100)
        self.floor_light = np.full((self.x_size, self.y_size), 100)

        #initial
        # self.ph_board = np.full((self.x_size, self.y_size),50)
        ph_row = [np.concatenate([np.full((int(self.x_size / 2), 1),25), np.full((int(self.x_size / 2), 1), 75)])]
        self.ph_board = ph_row
        for _ in range(self.y_size-1):
            self.ph_board = np.append(self.ph_board, ph_row, axis = 0)

    def update_light_boards(self, plant_list):
        '''updates light boards on the floor and understory.
        At the same time, calculates plants energy'''
        new_understory_light = np.full((self.x_size,self.y_size), 100)
        new_floor_light = np.full((self.x_size, self.y_size), 100)

        for plant in plant_list:
            new_energy = 100 * np.pi * (plant.radius_canopy)

            #Checks through squares on all levels to see if it the squares lie within its radius adds to the energy the brightness of the light at that level
            largest_radius = max(plant.radius_understory, plant.radius_floor, plant.radius_canopy)
            for boxx in range(plant.x_pos-largest_radius, plant.x_pos + largest_radius):
                for boxy in range(plant.y_pos -largest_radius, plant.y_pos + largest_radius):
                    if boxx >= 0 and boxy >=0 and boxy < self.y_size and boxx < self.x_size:
                        distance = (plant.x_pos - boxx)**2 + (plant.y_pos - boxy)**2

                        if distance <= plant.radius_canopy ** 2 and plant.radius_canopy != 0:
                            new_understory_light[boxy, boxx] = new_understory_light[boxy, boxx] * (1-self.leaf_obscurity)
                            new_floor_light[boxy, boxx] = new_floor_light[boxy, boxx] * (1-self.leaf_obscurity)

                        if distance <= plant.radius_understory ** 2 and plant.radius_understory != 0:
                            new_floor_light[boxy, boxx] = new_floor_light[boxy, boxx] * (1-self.leaf_obscurity)
                            new_energy += self.understory_light[boxy, boxx]

                        if distance <= plant.radius_floor ** 2 and plant.radius_floor != 0:
                            new_energy += self.floor_light[boxy, boxx]

            required_sustenance = np.pi * (plant.radius_floor**2 + plant.radius_understory**2 + plant.radius_canopy**2) * self.area_energy_cost

            #Calculates plant energy with pdf centered at the
            plant.energy = new_energy * (norm.pdf(self.ph_board[plant.y_pos, plant.x_pos], plant.soil_ph, self.efficiency_stdev)/norm.pdf(plant.soil_ph, plant.soil_ph, self.efficiency_stdev)) - required_sustenance

        self.understory_light = new_understory_light
        self.floor_light = new_floor_light

    def next_year(self, plant_list):
        '''Runs the simulation for one year. Updates boards etc'''

        self.update_light_boards(plant_list)
        male_list = []
        female_list = []

        #Loop through all plants
        for plant in plant_list:
            if plant.age == self.max_age or plant.energy < 0 : #kill all plants which do not reach a minimum energy input
                plant_list.remove(plant) #kill all plants above max_age
            else:
                plant.age += 1 #age all plants

                #Grows height and radius if not maximized
                if plant.height <= plant.growth_height and plant.energy > self.height_growth_cost:

                    plant.height += 1
                    plant.energy -= self.height_growth_cost

                #test interaction by looping through all distances
                can_grow_floor, can_grow_understory, can_grow_canopy = plant.check_can_grow(plant_list) #
                #grow all plants which have the energy to do so, have not reached their growth limit and do not interact with other trees
                if plant.height == 3 and plant.radius_canopy < plant.max_rad_can and can_grow_canopy and plant.energy > np.pi * (2 * plant.radius_canopy + 1) * self.area_growth_cost:
                    plant.radius_canopy += 1
                    plant.energy -= np.pi * (2 * plant.radius_canopy + 1) * self.area_growth_cost #area_growth_cost times the change in area of the canopy

                if plant.height >= 2 and plant.radius_understory < plant.max_rad_und and can_grow_understory and plant.energy > np.pi * (2 * plant.radius_understory + 1) * self.area_growth_cost:
                    plant.radius_understory += 1
                    plant.energy -= np.pi * (2 * plant.radius_understory + 1) * self.area_growth_cost

                if plant.radius_floor < plant.max_rad_flo and can_grow_floor and plant.energy > np.pi * (2 * plant.radius_understory + 1) * self.area_growth_cost:
                    plant.radius_floor += 1
                    plant.energy -= np.pi * (2 * plant.radius_floor + 1) * self.area_growth_cost

                if plant.gender:#adds to male or female plant lists
                    for _ in range(int(min(plant.seed_production, plant.energy//self.seed_cost))):
                        male_list.append(plant)
                else:
                    female_list.append(plant)

        random.shuffle(male_list)

        for seed in male_list:
            if len(plant_list) < self.y_size * self.x_size:
                random.shuffle(female_list)
                for female in female_list:
                    if seed.check_viable_mate(self.min_dist, female):
                        not_chosen = True
                        #generates random x,y variable which is not yet taken up
                        while not_chosen:
                            new_x = random.randint(0, self.x_size -1)
                            new_y = random.randint(0, self.y_size -1)
                            if (new_y,new_x) not in plant_list:
                                not_chosen = False
                        plant_list.append(create_seed(seed, female, new_x, new_y, self.mutation_rate))
                        break
            else:
                break
        return(plant_list)

class Plant:
    def __init__(self, soil_ph, seed_production, growth_height, max_rad_flo, max_rad_und, max_rad_can, gender:bool, x_pos = None , y_pos = None):

        #Genetic Information
        self.soil_ph = soil_ph
        self.seed_production = seed_production
        self.growth_height = growth_height
        self.max_rad_flo = max_rad_flo
        self.max_rad_und = max_rad_und
        self.max_rad_can = max_rad_can
        self.gender = gender # True is Male, False is Female

        #Plant information
        self.height = 1
        self.radius_canopy = 0
        self.radius_understory = 0
        self.radius_floor = 2
        self.x_pos = x_pos
        self.y_pos = y_pos
        self.age = 0
        self.display_color = (0, min(255, math.floor(255/100 * self.soil_ph)), 0)
        self.energy = None

    def __eq__(self, coords):
        '''returns true if tuple is equal to x,y coordinates'''

        if isinstance(coords, tuple):
            if coords[0] == self.y_pos and coords[1] == self.x_pos:
                return True
            return False
        if coords.y_pos == self.y_pos and coords.x_pos == self.x_pos:
            return True
        return False

    def check_viable_mate(self, min_dist, mate):
        '''Returns whether two plants can mate(are of the same species)'''
        dist_to_mate = (self.soil_ph - mate.soil_ph)**2 + ((self.growth_height - mate.growth_height)*100/3)**2 + (self.seed_production - mate.seed_production)**2
        if dist_to_mate <= min_dist:
            return True
        return False

    def check_can_grow(self, plant_list):
        '''Outputs a tuple of booleans determining whether the plant has space to grow'''
        can_grow_floor = True
        can_grow_understory = True
        can_grow_canopy = True

        for p in plant_list:
            if not self.__eq__(p):
                distance = ((p.x_pos - self.x_pos)**2 + (p.y_pos - self.y_pos)**2)**0.5

                if can_grow_floor:
                    can_grow_floor = (distance - p.radius_floor) > (self.radius_floor + 1) or distance < self.radius_floor

                if can_grow_understory and self.height >= 2 and p.height >= 2:
                    can_grow_understory = (distance - p.radius_understory) > (self.radius_understory + 1) or distance < self.radius_understory

                if can_grow_canopy == True and self.height == 3 and p.height == 3:
                    can_grow_canopy = (distance - p.radius_canopy) > (self.radius_canopy + 1) or distance < self.radius_canopy

        return(can_grow_floor, can_grow_understory, can_grow_canopy)

def create_seed(parent1, parent2, x, y, mutation_rate):
    '''Creates a new seed in the plant class.
    All of the seeds variables are selected using a normal distribution where the average of the
    parents variables is the mean and the mutation_rate is the standard deviation
    '''

    seed_soil_ph = np.random.normal((parent1.soil_ph + parent2.soil_ph)/2, mutation_rate * 20)
    if seed_soil_ph < 0:
        seed_soil_ph = 0

    seed_seed_production = math.ceil(np.random.normal((parent1.seed_production + parent2.seed_production)/2, mutation_rate * 10))
    if seed_seed_production < 0:
        seed_seed_production = 0

    seed_growth_height = random.choice([random.choice([parent1.growth_height, parent2.growth_height]),random.randint(1,3)]) # 4/9 times

    seed_rad_flo = math.ceil(np.random.normal((parent1.max_rad_flo + parent2.max_rad_flo)/2, mutation_rate * 10))
    if seed_rad_flo <1:
        seed_rad_flo = 1

    seed_rad_und = math.ceil(np.random.normal((parent1.max_rad_und + parent2.max_rad_und)/2, mutation_rate * 10))
    if seed_rad_und <1:
        seed_rad_und = 1

    seed_rad_can = math.ceil(np.random.normal((parent1.max_rad_can + parent2.max_rad_can)/2, mutation_rate * 10))
    if seed_rad_can <1:
        seed_rad_can = 1

    seed = Plant(seed_soil_ph, seed_seed_production, seed_growth_height, seed_rad_flo, seed_rad_und, seed_rad_can, bool(random.getrandbits(1)), x, y)
    return seed

def initiate_plants(num_plants: int, y_size: int, x_size):
    x_list = np.random.choice(range(x_size), num_plants, replace= False).tolist()
    y_list = np.random.choice(range(y_size), num_plants, replace= False).tolist()
    plant_list = []

    for _ in range(num_plants):

        soil_ph = random.uniform(1,100)
        growth_height = random.randint(1,3)
        seed_production = random.randint(1, 5)

        #need to finish
        rad_flo = random.randint(1, 30)
        rad_und = random.randint(1, 30)
        rad_can = random.randint(1, 30)
        x = x_list.pop()
        y = y_list.pop()

        plant_list.append(Plant(soil_ph, seed_production, growth_height, rad_flo, rad_und, rad_can, bool(random.getrandbits(1)),x, y))

    return plant_list


from helper.input import read_input_simple
import sys

def parse_input(input):
    sensors = []
    for line in read_input_simple(15, input):
        line = line.split(' ')
        x1 = int(line[2][2:-1])
        y1 = int(line[3][2:-1])
        x2 = int(line[8][2:-1])
        y2 = int(line[9][2:])
        sensors.append((x1, y1, x2, y2))
    return sensors

def part1(input):
    sensors = parse_input(input)
    # y_cutoff = 10
    y_cutoff = 2000000
    ranges = list()
    for sx, sy, bx, by in sensors:
        # print(f'Sensor at ({sx}, {sy}) with beacon at ({bx}, {by})')
        d_sb = abs(sx - bx) + abs(sy - by)
        d_sl = abs(sy - y_cutoff)
        diff = d_sb - d_sl
        if diff >= 0:
            ranges.append([sx - diff, sx + diff])

    ranges.sort()
    merged_ranges = [ranges[0]]
    for i in range(1, len(ranges)):
        if ranges[i][0] > merged_ranges[-1][1] + 1:
            merged_ranges.append(ranges[i])
        else:
            merged_ranges[-1][1] = max(ranges[i][1], merged_ranges[-1][1])
    return sum([x2 - x1 for x1, x2 in merged_ranges])

def part2(input):
    sensors = parse_input(input)
    # bound = 20
    bound = 4000000
    ranges = []
    for y_cutoff in range(bound + 1):
        if y_cutoff % 100000 == 0:
            print(f'{100.0 * y_cutoff / bound}%')
        cant_contain = list()
        for sx, sy, bx, by in sensors:
            # print(f'Sensor at ({sx}, {sy}) with beacon at ({bx}, {by})')
            d_sb = abs(sx - bx) + abs(sy - by)
            d_sl = abs(sy - y_cutoff)
            diff = d_sb - d_sl
            if diff >= 0:
                cant_contain.append([max(sx - diff, 0), min(sx + diff, bound)])
        cant_contain.sort()
        merged_ranges = [cant_contain[0]]
        for i in range(1, len(cant_contain)):
            if cant_contain[i][0] > merged_ranges[-1][1] + 1:
                merged_ranges.append(cant_contain[i])
            else:
                merged_ranges[-1][1] = max(cant_contain[i][1], merged_ranges[-1][1])
        ranges.append(merged_ranges)
    for y, rang in enumerate(ranges):
        if rang != [[0, bound]]:
            x = rang[0][1] + 1
            return y + x * 4000000

if __name__ == '__main__':
    _, part, input = sys.argv
    if part == '1':
        print(part1(input))
    elif part == '2':
        print(part2(input))
    else:
        print('Part must be one of 1 or 2')


# -*- coding: utf-8 -*-
# @Author  : qiaohezhe
# @github : https://github.com/fengduqianhe
# @Date    :  8/31/2022
# version： Python 3.7.8
# @File : one_class_homo.py
# @Software: PyCharm
# @Institution: SMU

import numpy as np
import scipy.io as sio
import scipy.sparse as sp
from collections import Counter
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
from matplotlib.ticker import MaxNLocator

plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['figure.dpi'] = 300  # 图片像素
plt.rcParams['figure.figsize'] = (8.5, 7.5)
dataset_name = 'Facebook'

data = sio.loadmat('./data/{}.mat'.format(dataset_name))

label = data['Label'] if ('Label' in data) else data['gnd']

attr = data['Attributes'] if ('Attributes' in data) else data['X']
network = data['Network'] if ('Network' in data) else data['A']

attr = attr.toarray()
adj_matrix = network.toarray()

label = label[:, 0]
print(Counter(label))

plt.rcParams['figure.figsize'] = (8.5, 7.5)
from matplotlib.backends.backend_pdf import PdfPages

import matplotlib.pyplot as plt
import matplotlib.mlab as mlab

def draw_homo(message_all, i):
    with PdfPages('pdf/BlogCatalog.pdf') as pdf:
        mu_0 = np.mean(message_all[0])  # 计算均值
        print('The mean of normal node is {}'.format(mu_0))
        sigma_0 = np.std(message_all[0])
        print('The std of normal node is {}'.format(sigma_0))
        mu_1 = np.mean(message_all[1])  # 计算均值
        print('The mean of abnormal node is {}'.format(mu_1))
        sigma_1 = np.std(message_all[1])
        print('The std of abnormal node is {}'.format(sigma_1))
        n, bins, patches = plt.hist(message_all, bins=30, normed=1, label=['Normal', 'Abnormal'])
        y_0 = mlab.normpdf(bins, mu_0, sigma_0)  # 拟合一条最佳正态分布曲线y
        y_1 = mlab.normpdf(bins, mu_1, sigma_1)  # 拟合一条最佳正态分布曲线y
        # plt.plot(bins, y_0, 'g--', linewidth=3.5)  # 绘制y的曲线
        # plt.plot(bins, y_1, 'r--', linewidth=3.5)  # 绘制y的曲线
        plt.plot(bins, y_0, color='steelblue', linestyle='--', linewidth=7.5)  # 绘制y的曲线
        plt.plot(bins, y_1, color='darkorange', linestyle='--', linewidth=7.5)  # 绘制y的曲线
        plt.ylim(0, 100)
        plt.xlabel('Homophily', fontsize=25)
        plt.ylabel('Number of Samples', size=25)
        plt.yticks(fontsize=30)
        plt.xticks(fontsize=30)
        plt.gca().yaxis.set_major_locator(MaxNLocator(integer=True))
        plt.legend(loc='upper left', fontsize=30)
        plt.title('{}_{}'.format(dataset_name, i), fontsize=25)
        plt.title('Facebook', fontsize=25)
        plt.show()
        # plt.savefig('fb_homo.png', dpi=500, bbox_inches='tight')

def cmpute_home(adj_matrix, anomaly_ids):
    row = adj_matrix.shape[0]
    col = adj_matrix.shape[1]
    num_ano_list = []

    for i in range(row):
        node_index = np.argwhere(adj_matrix[i, :] == 1)[:, 0]
        num_ano = len(set(node_index) & set(anomaly_ids))
        if len(node_index) != 0:
            num_ano_list.append(num_ano / len(node_index))
        else:
            num_ano_list.append(-1)
    return np.array(num_ano_list)

normal_node_index = np.where(label == 0)[0]
abnormal_node_index = np.where(label == 1)[0]

homo1 = cmpute_home(adj_matrix, normal_node_index)
homo2 = cmpute_home(adj_matrix, abnormal_node_index)
normal_node_degree = homo1[normal_node_index]
abnormal_node_degree = homo2[abnormal_node_index]

data = [normal_node_degree, abnormal_node_degree]
fig, ax = plt.subplots()
ax.boxplot(data)
# plt.ylim(0, 1)
ax.set_xticklabels(["normal", "abnormal"])  # 设置x轴刻度标签
plt.title('Node Homophily')
plt.show()

draw_homo(data, 0)


import discord
from discord.ext import commands

import asyncio
import random
import youtube_dl

from collections import deque

import re
from bs4 import BeautifulSoup

import functools

ANILIST_API = 'https://graphql.anilist.co'
ANILIST_QUERY = '''query GetAnimeList($name: String) {MediaListCollection(userName: $name, type: ANIME) {
    lists {
        entries{
            media{
                idMal
                seasonYear
                title {
                    romaji
                    english
                }
                synonyms
                coverImage {
                    extraLarge
                }
            }
        }
    }
}}'''

wiki = "https://www.reddit.com/r/AnimeThemes/wiki/"

ytdl_format_options = {
    'format': 'bestaudio/best',
    'outtmpl': '%(extractor)s-%(id)s-%(title)s.%(ext)s',
    'restrictfilenames': True,
    'noplaylist': True,
    'nocheckcertificate': True,
    'ignoreerrors': False,
    'logtostderr': False,
    'quiet': True,
    'no_warnings': True,
    'default_search': 'auto',
    'source_address': '0.0.0.0' # bind to ipv4 since ipv6 addresses cause issues sometimes
}

ffmpeg_options = {
    'options': '-vn',
    'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5'
}

ytdl = youtube_dl.YoutubeDL(ytdl_format_options)

class YTDLError(Exception):
    pass

class YTDLSource(discord.PCMVolumeTransformer):
    def __init__(self, source, *, data, volume=0.5):
        super().__init__(source, volume)

        self.data = data

        self.title = data.get('title')
        self.url = data.get('url')

    @classmethod
    async def create_source(cls, search: str, loop: asyncio.BaseEventLoop = None, stream=False):
        loop = loop or asyncio.get_event_loop()

        partial = functools.partial(ytdl.extract_info, search, download=False, process=False)
        data = await loop.run_in_executor(None, partial)

        if data is None:
            raise YTDLError('Couldn\'t find anything that matches `{}`'.format(search))

        if 'entries' not in data:
            process_info = data
        else:
            process_info = None
            for entry in data['entries']:
                if entry:
                    process_info = entry
                    break

            if process_info is None:
                raise YTDLError('Couldn\'t find anything that matches `{}`'.format(search))

        webpage_url = process_info['webpage_url']
        partial = functools.partial(ytdl.extract_info, webpage_url, download=False)
        processed_info = await loop.run_in_executor(None, partial)

        if processed_info is None:
            raise YTDLError('Couldn\'t fetch `{}`'.format(webpage_url))

        if 'entries' not in processed_info:
            info = processed_info
        else:
            info = None
            while info is None:
                try:
                    info = processed_info['entries'].pop(0)
                except IndexError:
                    raise YTDLError('Couldn\'t retrieve any matches for `{}`'.format(webpage_url))

        return cls(discord.FFmpegPCMAudio(info['url'], **ffmpeg_options), data=info)

class WeebGame():
    def __init__(self, guild_id):
        self.guild_id = guild_id

        self.lists = {}
        self.length = 0

        self.scores = {}
        self.players = {}
        self.total = 0
        self.rounds = 0

    def get_length(self):
        total = set()
        for i in self.lists:
            ids = [anime["media"]["idMal"] for anime in self.lists[i]]
            total.update(ids)
        return len(total)

    def add_list(self, name, data):
        self.lists[name] = data
        self.length = self.get_length()
        return self.current_lists()

    def remove_list(self, name):
        if not name in self.lists:
            raise Exception()
        del self.lists[name]
        return self.current_lists()

    def current_lists(self):
        current_lists = list(self.lists.keys())
        if len(current_lists) == 0:
            current_lists = ["empty :("]
        embed = discord.Embed(
            title = "Anime Music Game - Current Lists",
            description = "\n".join(current_lists),
            color = 0xe475ea
        )
        return embed

    def random_anime(self):
        anime_list = random.choice(list(self.lists.values()))
        anime = random.choice(anime_list)["media"]
        idMal = anime["idMal"]
        year = anime["seasonYear"]
        cover = anime["coverImage"]["extraLarge"]
        name = anime["title"]["romaji"]
        english = anime["title"]["english"]
        synonyms = anime["synonyms"]
        synonyms.append(english)

        print(name, year, synonyms)

        return anime, idMal, year, cover, name, synonyms

    def score_string(self):
        combined = [(self.scores[i], self.players[i].display_name) for i in self.scores]
        ordered = list(reversed(sorted(combined)))
        formatted = [f"**{i[1]}** has {i[0]} points" for i in ordered]
        joined = "\n".join(formatted)

        if len(joined) == 0:
            return "none :("
        return joined

    def increment_score(self, user : discord.Member, name, title, thumbnail):
        if not user.id in self.scores:
            self.scores[user.id] = 0
            self.players[user.id] = user

        self.scores[user.id] += 5

        self.total += 1
        self.rounds += 1

        player = self.players[user.id]

        # make an embed
        embed = discord.Embed(
            title = "Anime Music Game",
            description = f'That was __{title}__ from *{name}*',
            color = player.color
        )
        embed.set_thumbnail(url = thumbnail)
        embed.set_author(name = f"{player.display_name} guessed it!", icon_url = player.avatar_url)
        embed.add_field(name = "Scores", value = self.score_string())
        return embed

    def failure(self, name, title, thumbnail):
        self.rounds += 1
        embed = discord.Embed(
            title = "Anime Music Game",
            description = f'That was __{title}__ from *{name}*',
            color = 0xcc0000
        )
        embed.set_thumbnail(url = thumbnail)
        embed.set_author(name = "no one got it this round :(")
        embed.add_field(name = "Scores", value = self.score_string())
        return embed

    def end_embed(self):
        embed = discord.Embed(
            title = "Anime Music Game",
            description = (f'That was {5*self.total} points across {self.rounds} rounds ' +
                            '({:.2f}% accuracy)'.format(100*self.total/self.rounds)),
            color = 0x98d685
        )
        embed.add_field(name = "Scores", value = self.score_string())
        return embed

    def listen_embed(self, name, title, thumbnail):
        embed = discord.Embed(
            title = "Anime Music",
            description = f'This is __{title}__ from *{name}*',
            color = 0xc0c0c0
        )
        embed.set_image(url = thumbnail)
        return embed

class WeebMusic(commands.Cog):
    def __init__(self, bot):
        self.bot = bot
        self.wiki_cache = {}
        self.games = {}
        self.pasts = [deque(), set()]

    async def random_title(self, idMal, year, name):
        # scrape reddit
        if year == None:
            raise Exception()

        if year < 2000:
            year = str(year)[2] + "0s"

        if not year in self.wiki_cache:
            async with self.bot.session.get(wiki + str(year)) as resp:
                soup = BeautifulSoup(await resp.text(), "html.parser")
            self.wiki_cache[year] = soup

        soup = self.wiki_cache[year]

        link = "https://myanimelist.net/anime/{}/".format(idMal)
        matches = soup.select('h3 a[href="{}"]'.format(link))

        if len(matches) == 0:
            print("no matches")
            raise Exception()

        elem = matches[0]
        table = elem.parent.find_next("tbody")

        first_col = [i.find("td").string for i in table.find_all("tr")]
        choices = list(filter(lambda x: x and not x.isspace(), first_col))

        title = random.choice(choices)
        clean_title = title.replace("\"", "")
        full_title = f"{name} {clean_title}"
        return full_title

    async def get_random(self, guild_id):
        # i really need this to return something eventually, but theres a lot of points of failure
        # rather than try to handle all of them, i just retry when something fails
        try:
            # choose from lists
            anime, idMal, year, cover, name, synonyms = self.games[guild_id].random_anime()

            title = await self.random_title(idMal, year, name)

            # avoid repeats
            while len(self.pasts[0]) >= self.games[guild_id].length:
                self.pasts[1].remove(self.pasts[0].pop())
            if title in self.pasts[1]:
                print("repeat")
                raise Exception()
            else:
                self.pasts[0].appendleft(title)
                self.pasts[1].add(title)

            print(title)

            # construct player
            player = await YTDLSource.create_source(title, loop=self.bot.loop, stream=True)

            return (name, cover, title, player, synonyms)
        except Exception as e:
            print(e)
            if guild_id in self.games:
                return await self.get_random(guild_id)

    def longest_common_subsequence(self, A, B):
        x = len(A)
        y = len(B)
        z = 0

        L = [[0 for j in range(y)] for i in range(x)]

        for i in range(x):
            for j in range(y):
                if A[i] == B[j]:
                    if i == 0 or j == 0:
                        L[i][j] = 1
                    else:
                        L[i][j] = L[i - 1][j - 1] + 1
                    if L[i][j] > z:
                        z = L[i][j]
        return z

    def guess_match(self, source, target):
        if source == None or target == None:
            return False

        def clean_string(s):
            return re.sub(r"[\W_]+", " ", s.lower()).split(" ")

        source_list = clean_string(source)
        target_list = clean_string(target)

        if source == target:
            return True

        if source_list[0] in target_list and len(source_list[0]) >= 6:
            return True

        # source_list is a sublist of target_list of at least length 2
        lcs = self.longest_common_subsequence(source_list, target_list)
        if len(source_list) == lcs and lcs >= 2:
            return True

        return False

    async def handle_controls(self, ctx, event, message, game):
        await message.add_reaction('▶')

        def check(reaction, user):
            return (reaction.message.id == message.id
                and str(reaction.emoji) == '▶'
                and user.id in game.players
            )

        def skip_check(reaction, user):
            return (reaction.message.id == message.id
                and str(reaction.emoji) == '⏭'
                and user.id in game.players
            )

        try:
            reaction, user = await self.bot.wait_for('reaction_add', timeout=5.0, check=check)
            await message.add_reaction('⏭')
            skip_task = self.bot.wait_for('reaction_add', check=skip_check)
            # wait for song end or skip react, whichever comes first
            _, pending = await asyncio.wait([event.wait(), skip_task],
                                            return_when=asyncio.FIRST_COMPLETED)
            for task in pending:
                task.cancel()

        except asyncio.TimeoutError:
            pass

    async def game_step(self, ctx, song, game):
        (name, cover, title, player, synonyms) = song

        event = asyncio.Event()

        # stop before play lets previous song play until next song, eliminating awk gap
        ctx.voice_client.stop()
        ctx.voice_client.play(player, after=lambda e: event.set())

        print("playing")

        def check(m):
            return (m.channel.id == ctx.channel.id and
                (self.guess_match(m.content, name) or
                    any([self.guess_match(m.content, i) for i in synonyms]))
            )

        try:
            message = await self.bot.wait_for('message', timeout=30.0, check=check)
            embed = self.games[ctx.guild.id].increment_score(message.author, name, title, cover)
            m = await ctx.send(embed = embed)
            # handle if someone presses continue
            await self.handle_controls(ctx, event, m, game)

        except asyncio.TimeoutError:
            embed = self.games[ctx.guild.id].failure(name, title, cover)
            await ctx.send(embed=embed)

    async def game_loop(self, ctx, game):

        song = await self.get_random(ctx.guild.id)
        while game.guild_id in self.games:
            try:
                await self.game_step(ctx, song, game)
                song = await self.get_random(ctx.guild.id)
            except Exception as e:
                print(e)
                song = await self.get_random(ctx.guild.id)

    @commands.group(aliases=["am"], invoke_without_command=True)
    async def animu(self, ctx):
        embed = discord.Embed(
            title = "Anime Music Game",
            description = " ".join([
                "Test your weeb level against fellow weebs!",
                "Join a voice channel, add at least one anime list using `?am add_list [anilist username]`, "
                "and start the game using `?am play`.",
                "\n\nWhen you recognize what anime you think it's from, simply type out your guess.",
                "You get 5 points for every correct guess, and there's no penalty for guessing.",
                "\n\nOnce you guess correctly, you can use ▶ to continue playing the song",
                "or ⏭ to skip to the next one.",
                "The next song will start automatically in 5 seconds.",
                "\n\nIf you ever want to stop the game, just type out `?am stop`",
                "and the game will tally up your score.",
                "\n\nHave fun!"
            ]),
            color = 0xe475ea
        )

        embed.set_footer(text=f"i am not liable for any time lost playing this game (image credits to @deeeskye)")
        embed.set_image(url="https://i.pinimg.com/originals/34/b2/2e/34b22e8d6c9be0f9d5f09b0295d65747.jpg")
        await ctx.send(embed=embed)

    @animu.command()
    async def play(self, ctx):
        if not ctx.guild.id in self.games:
            await ctx.send("no lists, can't start!")
            return

        if ctx.author.voice == None:
            await ctx.send("hmm, r u sure ur in a voice channel?")
            return

        channel = ctx.author.voice.channel

        try:
            await channel.connect()
        except discord.errors.ClientException as e:
            await ctx.send("woops! can't do that...")
            print(e)
            return

        embed = discord.Embed(
            title = "Anime Music Game",
            description = "Starting!",
            color = 0xe475ea
        )
        await ctx.send(embed=embed)

        await self.game_loop(ctx, self.games[ctx.guild.id])

    @animu.command()
    async def remove_list(self, ctx, name):
        try:
            embed = self.games[ctx.guild.id].remove_list(name)
            await ctx.send(embed=embed)
        except Exception as e:
            print(e)
            await ctx.send("failed")

    @animu.command()
    async def add_list(self, ctx, name):
        async with self.bot.session.post(ANILIST_API, json={
                'query': ANILIST_QUERY,
                'variables': {
                    'name': name
                }
        }) as response:
                if response.status != 200:
                    await ctx.send("failed")
                    print(response)
                else:
                    data = (await response.json())["data"]["MediaListCollection"]["lists"]
                    if not ctx.guild.id in self.games:
                        self.games[ctx.guild.id] = WeebGame(ctx.guild.id)
                    embed = self.games[ctx.guild.id].add_list(name, data[0]["entries"])
                    await ctx.send(embed=embed)

    @animu.command()
    async def stop(self, ctx):
        voice = discord.utils.get(self.bot.voice_clients, guild=ctx.guild)
        await voice.disconnect()
        await ctx.send(embed=self.games[ctx.guild.id].end_embed())
        del self.games[ctx.guild.id]

def setup(bot):
    bot.add_cog(WeebMusic(bot))


import os
from operator import attrgetter
from distutils.dir_util import mkpath

import pandas as pd
import matplotlib.pyplot as plt

try:
    from __init__ import ROOT
except ImportError:
    ROOT = None

from misc import matching
from misc import utils

plt.ion()
plt.rc("axes.spines", top=False, right=False)

GIW_EVENT_MAPPING = {1: 1, 2: 4, 3: 2, 4: 5, 5: 12}
GIW_EVENT_MAPPING_REV = {v: k for k, v in GIW_EVENT_MAPPING.items()}

##
# ELC
def elc(evt_gt, evt_pr, ws, data_gt, data_pr, fpath_elc_mat, **kwargs):
    """Interface to matching framework"""
    # TODO: simply parse copnfusion matrix from ELCCM.m
    events_match = elc_tp_matching(evt_gt, evt_pr, ws)
    events_match = events_match.query("match==True")

    # load ELC alignment
    if not os.path.exists(fpath_elc_mat):
        print("Run ELCCM.m")
        _, fname = os.path.split(fpath_elc_mat)
        prepare_for_elc_mat(data_gt.copy(), data_pr.copy(), fname)

    data_mat_gt, data_mat_pr, data_mat = parse_elc_mat(fpath_elc_mat)
    matcher_elc_mat = get_elc_mat_events(data_mat, [["ref_aligned"], ["test_aligned"]])
    events_err = matcher_elc_mat.events
    events_err = events_err.query("match==True")

    events = pd.concat((events_match, events_err))
    events.reset_index(drop=True, inplace=True)

    output = events[["gt", "pr"]].values

    return output, events

def elc_tp_matching(evt_gt, evt_pr, ws, eps=1e-5):
    """Transition point matching for ELC visualization"""
    _ws = ws / 2 + eps

    # ELC transition points and matches
    accum_tp = []
    ag = attrgetter("st", "et", "evt")
    _evt_pr = evt_pr["evt"].values
    # find matching transition points
    for evt in evt_gt.itertuples():
        st, et, _evt = ag(evt)
        pr_onset = evt_pr.query("st>@st-@_ws and st<@st+@_ws and evt==@_evt")
        pr_offset = evt_pr.query("et>@et-@_ws and et<@et+@_ws and evt==@_evt")
        pr_onset = pr_onset.index.values[0] if len(pr_onset) else -1
        pr_offset = pr_offset.index.values[0] if len(pr_offset) else -1
        if -1 not in [pr_onset, pr_offset]:
            match = _evt_pr[pr_onset] == _evt_pr[pr_offset]
        else:
            match = False
        accum_tp.append([pr_onset, pr_offset, match])
    tp_onset, tp_offset, mask_match = zip(*accum_tp)

    # gather data
    gt_idx = list(range(len(evt_gt)))
    tp_onset = zip(gt_idx, tp_onset)
    match_tp_onset = list(filter(lambda x: -1 not in x, tp_onset))
    tp_offset = zip(gt_idx, tp_offset)
    match_tp_offset = list(filter(lambda x: -1 not in x, tp_offset))

    match_tp_onset = pd.DataFrame(match_tp_onset, columns=["gt_idx", "pr_idx"])
    match_tp_onset["onset"] = True
    match_tp_offset = pd.DataFrame(match_tp_offset, columns=["gt_idx", "pr_idx"])
    match_tp_offset["offset"] = True
    events_match_tp = pd.merge(
        match_tp_onset, match_tp_offset, on=["gt_idx", "pr_idx"], how="outer"
    )
    events_match_tp.fillna(False, inplace=True)

    gt_idx_match = [_m for _f, _m in zip(mask_match, gt_idx) if _f]
    events_match_tp["match"] = events_match_tp["gt_idx"].isin(gt_idx_match)

    gt_idx = events_match_tp["gt_idx"]
    pr_idx = events_match_tp["pr_idx"]
    events_match_tp["gt"] = evt_gt.loc[gt_idx, "evt"].values
    events_match_tp["pr"] = evt_pr.loc[pr_idx, "evt"].values

    return events_match_tp

# mat functions
def prepare_for_elc_mat(data_gt, data_pr, name, sdir):
    """Prepares data to be imported to matlab"""

    data_gt.replace({"evt": GIW_EVENT_MAPPING_REV}, inplace=True)
    data_pr.replace({"evt": GIW_EVENT_MAPPING_REV}, inplace=True)
    data_gt["evt-test"] = data_pr["evt"]
    data_gt.to_csv(os.path.join(sdir, f"{name}-data.csv"), index=False)
    for d, tag in zip([data_gt, data_pr], ["gt", "pr"]):
        evt = utils.aggr_events_df(d["evt"])
        # get timestamps for event bounds; adjust for matlab's 1-based indexing
        evt["st"] = d.iloc[evt["s"].values]["t"].values
        evt["et"] = d.iloc[evt["e"].values - 1]["t"].values
        evt["s"] += 1
        evt.to_csv(os.path.join(sdir, f"{name}-{tag}.csv"), index=False)

def parse_elc_mat(fpath):
    data = utils.loadmat(fpath)

    labels_ref = utils.get_nested_item(data, ["label_ref", "Labels"])
    labels_test = utils.get_nested_item(data, ["label_test", "Labels"])

    timestamps = utils.get_nested_item(data, ["label_test", "T"])
    data_gt = pd.DataFrame.from_dict({"t": timestamps, "evt": labels_ref})
    data_pr = pd.DataFrame.from_dict({"t": timestamps, "evt": labels_test})

    return data_gt, data_pr, data

def get_elc_mat_events(data_mat, sources):
    _source_gt, _source_pr = sources
    labels_ref = utils.get_nested_item(data_mat, _source_gt)
    labels_test = utils.get_nested_item(data_mat, _source_pr)
    timestamps = utils.get_nested_item(data_mat, ["label_test", "T"])
    _gt = pd.DataFrame.from_dict({"t": timestamps, "evt": labels_ref})
    _pr = pd.DataFrame.from_dict({"t": timestamps, "evt": labels_test})
    _gt.replace({"evt": GIW_EVENT_MAPPING}, inplace=True)
    _pr.replace({"evt": GIW_EVENT_MAPPING}, inplace=True)
    matcher = matching.EventMatcher(gt=_gt, pr=_pr, mode="raw")
    match_mask = matcher.events["gt"] == matcher.events["pr"]
    matcher.events["match"] = ~match_mask
    return matcher

# plotting functions
def plot_elc(events, matcher, matcher_mat, plot_transitions=False, xlim=None):
    kwargs = {
        "wspace": 0,
        "hspace": 0.3,
        "top": 0.9,
        "bottom": 0,
        "left": 0.2,
        "right": 0.98,
    }

    fig, ax = plt.subplots(figsize=(3.2, 1.2 * 1.5))
    ax = [None, ax]
    fig.subplots_adjust(**kwargs)
    if xlim is not None:
        ax[1].set_xlim(*xlim)

    # scarf plots
    scarf_plot = utils.ScarfPlot(ax[1])
    scarf_plot.plot(matcher.evt_gt, label="Ground\ntruth")
    scarf_plot.plot(matcher.evt_pr, label="Prediction")

    # match plots
    plot_kwargs = {"ax": ax[1], "ids": (0, 1), "enum": True, "exclude_null": True}
    utils.match_plot(events, matcher.evt_gt, matcher.evt_pr, plot_kwargs=plot_kwargs)

    # plot aligned events
    scarf_plot.plot(matcher_mat.evt_gt, label="Aligned\nground\ntruth")
    scarf_plot.plot(matcher_mat.evt_pr, label="Aligned\nprediction")
    plot_kwargs = {
        "ax": ax[1],
        "ids": (2, 3),
        "enum": False,
        "linestyle": "--",
        "match-point": "mid",
    }
    utils.match_plot(matcher_mat.events, plot_kwargs=plot_kwargs)

    # visualize transition points
    if plot_transitions:
        match_plot_kwargs = {
            "ids": (0, 1),
            "enum": False,
            "exclude_null": True,
            "linestyle": "--",
            "linewidth": 0.75,
            "color": "green",
            "match-point": "s",
        }
        plot_kwargs.update(match_plot_kwargs)
        utils.match_plot(
            events,
            matcher.evt_gt,
            matcher.evt_pr,
            col_match="onset",
            plot_kwargs=plot_kwargs,
        )

        match_plot_kwargs = {
            "enum": False,
            "exclude_null": True,
            "linestyle": "--",
            "linewidth": 0.75,
            "color": "red",
            "match-point": "e",
        }
        plot_kwargs.update(match_plot_kwargs)
        utils.match_plot(
            events,
            matcher.evt_gt,
            matcher.evt_pr,
            col_match="offset",
            plot_kwargs=plot_kwargs,
        )

    return fig

##
def main():
    root = os.path.join(ROOT, "assets")
    dataset = "elc"
    sdir = os.path.join(ROOT, "results", "match-plots", "elc")
    mkpath(sdir)

    jobs = [
        {"fname": "elc", "plot_transitions": True},
        {"fname": "elc_fail", "plot_transitions": False},
        {"fname": "elc_fail_timing", "plot_transitions": False},
    ]

    for job in jobs:
        fname = job["fname"]
        data_gt = pd.read_csv(os.path.join(root, dataset, f"{fname}.csv"))
        data_pr = pd.read_csv(os.path.join(root, f"{dataset}_test", f"{fname}.csv"))

        event_matcher = matching.EventMatcher(gt=data_gt, pr=data_pr, mode="raw")
        events = elc_tp_matching(
            event_matcher.evt_gt, event_matcher.evt_pr, ws=10 / 300.0
        )

        # analyse data from matlab implementation
        fpath_elc_mat = os.path.join(
            root, f"{dataset}_test", "elccm", f"{fname}-{fname}_test.mat"
        )
        if not os.path.exists(fpath_elc_mat):
            print(f"Run ELCCM.m for {fname} and copy to {fpath_elc_mat}")
            prepare_for_elc_mat(data_gt, data_pr, fname, sdir)
            continue

        data_mat_gt, data_mat_pr, data_mat = parse_elc_mat(fpath_elc_mat)
        matcher_mat = get_elc_mat_events(data_mat, [["ref_aligned"], ["test_aligned"]])

        # plot
        _e = utils.idx2timestamp(event_matcher.evt_gt.iloc[-1]["e"] + 1, data_gt)
        plot_transitions = job.get("plot_transitions", False)
        fig = plot_elc(
            events,
            event_matcher,
            matcher_mat,
            plot_transitions=plot_transitions,
            xlim=(data_gt["t"].min(), _e),
        )
        fig.savefig(os.path.join(sdir, f"{fname}.eps"))
        fig.savefig(os.path.join(sdir, f"{fname}.png"))

if __name__ == "__main__":
    main()

# debug ELCCM.m output
# fig, ax = plt.subplots(figsize=(3.2, 2.4))
# ax = [None, ax]
# fig.subplots_adjust(**kwargs)
# _e = utils.idx2timestamp(event_matcher.evt_gt.iloc[-1]['e'] + 1, data_gt)
# ax[1].set_xlim(data_gt['t'].min(), _e)
#
# # scarf plots
# scarf_plot = utils.ScarfPlot(ax[1])
# scarf_plot.plot(event_matcher.evt_gt, label='Ground\ntruth')
# scarf_plot.plot(event_matcher.evt_pr, label='Test')
# sources = [
#     [['label_ref', 'Labels'], ['label_test', 'Labels']],
#     # [['ref_fu'], ['test_fu']],
#     [['ref_aligned'], ['test_aligned']],
# ]
# for source in sources:
#     matcher_elc = get_elc_mat_events(data_mat, source)
#     _source_gt, _source_pr = source
#     scarf_plot.plot(matcher_elc.evt_gt, label='-'.join(_source_gt))
#     scarf_plot.plot(matcher_elc.evt_pr, label='-'.join(_source_pr))


from sqlalchemy.orm import Session

from app import models, schemas

def create_prescription(db: Session, prescription: schemas.PrescriptionCreate):
    db_prescription = models.Prescription(username=prescription.username, doctor=prescription.doctor,
                                          medicines=prescription.medicines,
                                          date_time=prescription.date_time, done=prescription.done)
    db.add(db_prescription)
    db.commit()
    db.refresh(db_prescription)
    return db_prescription

def read_user_prescription(db: Session, username: str, only_pending: bool):
    if only_pending:
        return db.query(models.Prescription).filter(models.Prescription.username == username).filter(
            models.Prescription.done == False).all()

    return db.query(models.Prescription).filter(models.Prescription.username == username).all()


import streamlit as st
import gestion_BBD as bdd
import utils as ut

db = bdd.connexion(st.secrets["textkey"], "michelin-4180a")

ajout, dico_entree = ut.construction_form_sidebar()

if ajout:
    if (dico_entree['Name']!= ""):
        bdd.ecriture_data(db, 'Indicateur', dico_entree)
    else :
        st.sidebar.error("Please enter the name of an indicator !")

st.markdown('# Macro indicator research glossary')

st.write('''We are doing more and more studies to understand and visualize the different economic macro indicators.
However, in order to avoid doing the same research several times and facing unnecessary problems this small application
has been set up to list everything that has been done on this subject.''')

form_recherche = st.form("Recherche form")
texte_a_chercher = form_recherche.text_input("What do you want")
recherche = form_recherche.form_submit_button("Rechercher")

if not recherche:
    bdd.lecture_data(db, "Indicateur")

if recherche:
    docs = db.collection(u'Indicateur').where(u'Name', u'==', texte_a_chercher).stream()

    for doc in docs:
        bdd.presentation_indicateur(db, doc.to_dict())

    if(texte_a_chercher == ""):
        bdd.lecture_data(db, "Indicateur")


'''
< Face Recognition >

- 참고
블로그 : https://wiserloner.tistory.com/1198
유튜브 : https://www.youtube.com/watch?v=sz25xxF_AVE&t=28s

face recognition : https://velog.io/@wbsl0427/facerecognition%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-%EC%96%BC%EA%B5%B4%EC%9D%B8%EC%8B%9D-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8-%EB%A7%8C%EB%93%A4%EA%B8%B0

pip install face-recognition
pip install cmake
pip isntall dlib
'''

import cv2
import numpy as np
import face_recognition

imgElon = face_recognition.load_image_file('./img_file/musk.jpg')
imgElon = cv2.cvtColor(imgElon, cv2.COLOR_BGR2RGB)

imgTest = face_recognition.load_image_file('./img_file/musk_test.jpg')
imgTest = cv2.cvtColor(imgTest, cv2.COLOR_BGR2RGB)

# 얼굴 찾기

# 원본 이미지 얼굴 bbox 좌표값
faceLoc = face_recognition.face_locations(imgElon)[0]

# 이를 encoding하여 특정 128개의 값으로 만들어내는 것.
encodeElon = face_recognition.face_encodings(imgElon)[0]
# print(encodeElon)
# bbox 그리기
cv2.rectangle(imgElon, (faceLoc[3], faceLoc[0]), (faceLoc[1], faceLoc[2]),(255,0,255), 2)

# Test이미지
faceLocTest = face_recognition.face_locations(imgTest)[0]
encodeTest = face_recognition.face_encodings(imgTest)[0]
cv2.rectangle(imgTest, (faceLocTest[3], faceLocTest[0]), (faceLocTest[1], faceLocTest[2]),(255,0,255), 2)

# 원본 이미지와 테스트 이미지가 비슷한지 테스트
results = face_recognition.compare_faces([encodeElon], encodeTest)
# print(results)

faceDis = face_recognition.face_distance([encodeElon], encodeTest)
print(results, faceDis)

# bbox위에 글자 입력
cv2.putText(imgTest, f'{results} {round(faceDis[0],2)}', (50, 50), cv2.FONT_HERSHEY_COMPLEX,1,(0,0,255),2)

cv2.imshow('Elon Musk', imgElon)
cv2.imshow('Elon Test', imgTest)
cv2.waitKey(0)


"""
Definition of the :class:`Score` model.
"""
import pandas as pd
from django.db import models
from django_mri.models import help_text
from django_mri.models.managers.score import ScoreManager, ScoreQuerySet

class Score(models.Model):
    """
    Represents the :class:`~django_mri.models.metric.Metric` score for some
    brain :class:`~django_mri.models.region.Region` instance derived from a
    particular :class:`~django_analyses.models.run.Run` instance.
    """

    origin = models.ManyToManyField(
        "django_mri.Scan", help_text=help_text.SCORE_ORIGIN,
    )
    region = models.ForeignKey(
        "django_mri.Region",
        on_delete=models.CASCADE,
        help_text=help_text.SCORE_REGION,
        blank=True,
        null=True,
    )
    metric = models.ForeignKey(
        "django_mri.Metric",
        on_delete=models.CASCADE,
        help_text=help_text.SCORE_METRIC,
    )
    run = models.ForeignKey(
        "django_analyses.Run",
        on_delete=models.CASCADE,
        help_text=help_text.SCORE_RUN,
    )
    value = models.FloatField(
        blank=False, null=False, help_text=help_text.SCORE_VALUE
    )

    objects = ScoreManager.from_queryset(ScoreQuerySet)()

    def __str__(self) -> str:
        """
        Returns the string representation of this instance.

        Returns
        -------
        str
            Score string representation
        """
        if self.region:
            return f"{self.metric} [{self.region}] = {self.value}"
        return f"{self.metric} = {self.value}"

    def to_series(self) -> pd.Series:
        d = {
            "Run ID": self.run.id,
            "Origin": self.origin.values_list("id", flat=True) if self.origin else None,
            "Analysis": self.run.analysis_version.analysis.title,
            "Version": self.run.analysis_version.title,
            "Atlas": self.region.atlas.title if self.region else None,
            "Index": self.region.index if self.region else None,
            "Hemisphere": self.region.hemisphere if self.region else None,
            "Region": self.region.title if self.region else None,
            "Metric": self.metric.title,
            "Score": self.value,
        }
        d["Origin"] = d["Origin"][0] if len(d["Origin"]) == 1 else d["Origin"]
        return pd.Series(d, name=self.id)


import datetime

import matplotlib.dates as md
import netCDF4
import numpy as np
import pandas as pd
from matplotlib import pyplot as plt

'''Some helper routines to fix data'''

def fillTimeAndPowerVector(inptTime, spacing, inptPwr):
    outputTime = []
    outputPwr = []
    totDataPntsAdded = 0
    # Need to fill in gaps in the time vectors. Normally, time stamps are 15min (900 sec) apart, if this is not the case
    # missing time stamps need to be added.
    # This assumes good data in the first and last time stamp, and it does not check for equal spacing otherwise.
    for idx, ts in enumerate(inptTime):
        # Skip first entry, just use as is
        if idx == 0:
            outputTime.append(ts)
            outputPwr.append(inptPwr[idx])
        # Skip last entry, just use as is
        elif idx == inptTime.size - 1:
            outputTime.append(ts)
            outputPwr.append(inptPwr[idx])
        # Check if there is a gap greater 900 sec. If so, see how many multiples of 900 fit this gap and add these time
        # stamps.
        elif inptTime[idx + 1] - ts > spacing:
            outputTime.append(ts)
            outputPwr.append(inptPwr[idx])
            dy = inptPwr[idx + 1] - inptPwr[idx]
            dx = inptTime[idx + 1] - ts
            slope = dy / dx
            delta = int(np.floor((inptTime[idx + 1] - ts) / spacing))
            for i in range(1, delta):
                outputTime.append(inptTime[idx] + i * spacing)
                outputPwr.append(inptPwr[idx] + slope * i * spacing)
                totDataPntsAdded = totDataPntsAdded + 1
        # All other cases, just take what's already there.
        else:
            outputTime.append(ts)
            outputPwr.append(inptPwr[idx])

    print('Total data points added : ', totDataPntsAdded)
    return outputTime, outputPwr

'''LoadData helps with repeatedly loading data from well formed netCDF files, i.e., such files with a 'value' 'time'
    tuple.
    THIS ROUTINE IGNORES SCALING AT THIS POINT
'''

def loadData(filePath):
    nc = netCDF4.Dataset(filePath)
    value = np.asarray(nc.variables['value'])
    time = np.asarray(nc.variables['time'])

    # simplify
    value = value[0, :]
    time = time[0, :]
    timeStamps = [datetime.datetime.fromtimestamp(ts) for ts in time]

    startDate = datetime.datetime.fromtimestamp(time[0])
    endDate = datetime.datetime.fromtimestamp(time[-1])

    return value, time, timeStamps, startDate, endDate

# import time series

# St. Mary's total load
stmLoadP, stmTime, stmTimeStamps, stmStart, stmEnd = loadData(
    '../../GBSProjects/StMary/InputData/TimeSeriesData/RawData/StMaryTotalizerLoadkW.nc')

# Mt. Village's total load
mtvLoadP, mtvTime, mtvTimeStamps, mtvStart, mtvEnd = loadData(
    '../../GBSProjects/StMary/InputData/TimeSeriesData/RawData/MountainVillageTotalizerLoadkW.nc')
print('Patching Mt. Village Data...') #Needs to be patched first to yield useable master timestamp vector
mtvTime, mtvLoadP = np.array(fillTimeAndPowerVector(np.array(mtvTime[:]), 900, mtvLoadP))

# Clip Mt. Village TS to one year length using most recent year
mtvTime = mtvTime[-35040:]
mtvLoadP = mtvLoadP[-35040:]
mtvStart = datetime.datetime.fromtimestamp(mtvTime[0])
print('Mt. Village data set clipped to: Start date : ', mtvStart, ' End date: ', mtvEnd)
stmDt = stmTime[1:] - stmTime[:-1]
mtvDt = mtvTime[1:] - mtvTime[:-1]

# Adjust length of stmLoadP and stmTime vectors based on available data in Mt. Village TS
stmLoadP = stmLoadP[stmTime > mtvTime[-35040] - 1]
stmTime = stmTime[stmTime > mtvTime[-35040] - 1]
stmStart = datetime.datetime.fromtimestamp(stmTime[0])
print('St. Mary\'s data set clipped to: Start date : ', stmStart, ' End date: ', stmEnd)

print('Patching St. Mary\'s Data...')
stmTime, stmLoadP = np.array(fillTimeAndPowerVector(stmTime, 900, stmLoadP))
# Need to fix time stamps vector
stmTimeStamps = [datetime.datetime.fromtimestamp(ts) for ts in stmTime]

# Calculate total combined load
print('Calculating total combined load...')
totLoadP = stmLoadP + mtvLoadP
print('Peak load :', np.nanmax(totLoadP), ' kW')
print('Average combined load: ', np.nanmean(totLoadP), ' kW')
print('Standard deviation from average: ', np.nanstd(totLoadP), ' kW')

# Wind power data set
wtgP, wtgTime, wtgTimeStamps, wtgStart, wtgEnd = loadData(
    '../../GBSProjects/StMary/InputData/TimeSeriesData/RawWindData/Calc50mPowerDW52.nc')

# fix any data gaps
print('Patching wind power data...')
wtgTime, wtgP = np.array(fillTimeAndPowerVector(wtgTime, 600, wtgP))
wtgStart = datetime.datetime.fromtimestamp(wtgTime[0])
wtgEnd = datetime.datetime.fromtimestamp(wtgTime[-1])
print('Wind power data range: Start date: ', wtgStart, ' to ', wtgEnd )

# Need to reorganize the time series so that it starts where the load time-series start
# Entries 0 through 11520 need to appended at the end
wtgTime1 = wtgTime[:11521]
wtgTime2 = wtgTime[11521:]
wtgTime = np.append(wtgTime1, wtgTime2, axis=0)
wtgP1 = wtgP[:11521]
wtgP2 = wtgP[11521:]
wtgP = np.append(wtgP1, wtgP2, axis=0)
# Cut down to one year length
wtgP = wtgP[:52560]
wtgPSize10 = wtgP.size

# need to resample the time series to fit with the load data
# get actual time stamps
wtgTimeStamps = [datetime.datetime.fromtimestamp(ts) for ts in wtgTime[:52560]]
wtgSeries = pd.Series(wtgP, index=wtgTimeStamps)
wtgShrt = wtgSeries.resample('15T').mean()

wtgP = np.array(wtgShrt)
# Fill NaN with 0
wtgP[np.isnan(wtgP)] = 0
print('Wind power data set re-sampled from 10 to 15 min intervals. Size changed from: ', wtgPSize10, 'to ', wtgP.size)

genP = totLoadP - wtgP
genDP = (genP[1:] - genP[:-1]) / (15 * 60)

plt.figure(figsize=(8, 5.5))

# Matplotlib formated timestamps for plotting routines.
mpTimeStamps = md.date2num(stmTimeStamps)

# discretize the profile
# Generator fleet as per 'Saint-Marys-REF8' project file
gen1PMax = 499 #Cummins QSX15G9
gen2PMax = 611 #Caterpillar 3508
gen3PMax = 908 #Caterpillar 3512
MOL = 0#.15 # NOTE THE FILE SUPPLIED BY AVEC STATES 0% (!!!)
print('Assuming: ', 100*MOL, '% MOL')
# Minimum spinning reserve kept in the system
# NOTE: this is a brute force approach, but with data available better estimates are not possible.
srcPMin = 0.15*totLoadP
srcP = np.zeros(np.shape(wtgP)) + srcPMin

#print('Assuming base SRC : ', srcPMin, 'kW')

# isolate the data where there is more wind power than demand.
curtailedP = np.copy(genP)
curtailedP[genP > MOL * gen1PMax] = np.nan
curtailedP = -(curtailedP - MOL * gen1PMax)

# Adjust SRC calculation to either be base SRC or actual wind power contribution to total load, which ever is greater.
srcP[srcP < (wtgP - curtailedP)] = wtgP[srcP < (wtgP - curtailedP)] - curtailedP[srcP < (wtgP - curtailedP)]
srcP = srcP*0

# Curtailment condition
genP[genP < MOL*gen1PMax] = MOL*gen1PMax # Normal approach with MOL
print('Max SRC : ', np.max(srcP), ' kW')
print('Min SRC : ', np.min(srcP), ' kW')
# Find which generator is online based on generator loading
def dieselSchedule(genP, MOL, srcP, gen1PMax, gen2PMax, gen3PMax):
    genPAvail = np.zeros(np.shape(genP))
    chngCnt = 0
    runHours = 0

    for i, p in enumerate(genP):
        if p <= MOL*gen1PMax and srcP[i] == 0:
            genPAvail[i] = 0
        elif p < (gen1PMax - srcP[i]):
            genPAvail[i] = gen1PMax
            runHours = runHours + 15/60
        elif p < (gen2PMax - srcP[i]):
            genPAvail[i] = gen2PMax
            runHours = runHours + 15/60
        elif p < (gen3PMax - srcP[i]):
            genPAvail[i] = gen3PMax
            runHours = runHours + 15/60
        elif p < (gen1PMax + gen2PMax - srcP[i]):
            #print('Required: ', p + srcP[i], ' Available : ', gen1PMax + gen2PMax)
            genPAvail[i] = gen1PMax + gen2PMax
            runHours = runHours + 2*15/60
        elif p < (gen1PMax + gen3PMax - srcP[i]):
            #print('1 and 3, Load: ', p, ' SRC: ', srcP[i], 'wtgP : ', wtgP[i])
            genPAvail[i] = gen1PMax + gen3PMax
            runHours = runHours + 2*15/60
        elif p < (gen2PMax + gen3PMax - srcP[i]):
            print('2 and 3, Load: ', p, ' SRC: ', srcP[i])
            genPAvail[i] = gen2PMax + gen3PMax
            runHours = runHours + 2*15/60
        elif p < (gen1PMax + gen2PMax + gen3PMax - srcP[i]):
            print('Load: ', p, ' SRC: ', srcP[i])
            genPAvail[i] = gen1PMax + gen2PMax + gen3PMax
            runHours = runHours + 3*15/60
        else:
            genPAvail[i] = -100

        if chngCnt > 0 and (genPAvail[i]-genPAvail[i-1] < 0):
            genPAvail[i] = genPAvail[i-1]

        # Prevent excessive down transitions
        if i > 3:
            if (genPAvail[i] - genPAvail[i-1]) < 0:
                chngCnt = 4
            elif chngCnt > 0:
                chngCnt = chngCnt - 1
            else:
                chngCnt = 0

    return genPAvail, runHours

genPAvail, runHours = dieselSchedule(genP, MOL, srcP, gen1PMax, gen2PMax, gen3PMax)

print('Number of diesel configuration changes : ', np.sum(np.abs(np.sign(np.diff(genPAvail)))))
print('Total generator run-hours: ', runHours, ' h')
# Capacity factor
nonZeroGenPAvail = np.copy(genPAvail)
nonZeroGenPAvail[genPAvail == 0] = np.nan
print('Diesel capacity factor :', np.nanmean(genP/nonZeroGenPAvail))
print('Diesel kWh total: ', np.nansum(genP)/4)

print('Wind power curtailed :', np.nansum(curtailedP)/4, ' kWh')
print('Wind power available :', np.nansum(wtgP)/4, ' kWh')

essEMaxRng = [ 20, 50, 75, 100, 125, 150, 175, 200, 250, 300, 400, 500, 750, 1000, 1500, 2000, 2500, 3500, 5000]#, 10000, 15000, 17500, 20000, 25000, 35000, 50000, 75000, 100000]
wpRec = np.zeros(np.shape(essEMaxRng))
essPMax = 5000
runHoursArray = np.zeros(np.shape(essEMaxRng))
dieselEReductionArray = np.zeros(np.shape(essEMaxRng))
essPMaxAct = np.zeros(np.shape(essEMaxRng))
configChngTot = np.zeros(np.shape(essEMaxRng))
genCapFac = np.zeros(np.shape(essEMaxRng))

for j, essEMax in enumerate(essEMaxRng):
    essEIn = np.zeros(np.shape(curtailedP))
    essEIn[0] = 0#curtailedP[0] / 4
    essEOut = np.zeros(np.shape(curtailedP))
    essEOut[0] = 0
    essP = np.zeros(np.shape(curtailedP))
    essETot = 0 #0.5*essEMax
    SOC = np.zeros(np.shape(curtailedP))
    recoveredWtgE = 0
    expendedWtgE = 0
    genPNew = np.copy(genP)

    for idx, wp in enumerate(curtailedP[1:]):
        SOC[idx] = essETot/essEMax
        # if there is some curtailed power, try put it in the battery at max possible rate
        if wp > 0 and essETot < essEMax:
            essEOut[idx] = 0   # charging, so the cummulative discharing counter is reset
            dE = (essEMax - essETot)     # remaining energy capacity until battery full

            # if the available energy fits, and the available power does not exceed max battery power, just charge
            if wp/4 < dE and wp < essPMax:
                essEIn[idx] = essEIn[idx-1] + (wp/4)
                essP[idx] = -wp
                recoveredWtgE = recoveredWtgE + (wp/4)
                essETot = essETot + (wp/4)
            # if the available energy fits, but we are limited by the max battery power, charge at max battery power
            elif wp/4 < dE and wp >= essPMax:
                essEIn[idx] = essEIn[idx-1] + essPMax
                essP[idx] = -essPMax
                recoveredWtgE = recoveredWtgE + essPMax/4
                essETot = essETot + essPMax/4
            # if the available energy does not fit, but the available power is fine
            elif wp/4 >= dE and 4*dE < essPMax:
                essEIn[idx] = essEIn[idx - 1] + dE
                essP[idx] = -dE*4
                recoveredWtgE = recoveredWtgE + dE
                essETot = essETot + dE
            # if the available energy does not fit, and the available power exceeds battery power
            else:
                essEIn[idx] = essEIn[idx - 1] + essPMax/4
                essP[idx] = -essPMax
                recoveredWtgE = recoveredWtgE + essPMax/4
                essETot = essETot + essPMax/4
        # if there is no excess wind power, try discharge the battery at maximum power possible
        elif np.isnan(wp) and essETot > 10:
            essEIn[idx] = 0  # discharging, so the cummulative charging counter is reset
            # if the requested power exceeds battteru power and requested energy is available, discharge at max power
            if genP[idx] > essPMax and essETot > essPMax/4:
                essETot = essETot - (essPMax/4)
                essEOut[idx] = essEOut[idx - 1] + (essPMax/4)
                essP[idx] = essPMax
                expendedWtgE = expendedWtgE + (essPMax/4)
                genPNew[idx] = genPNew[idx] - essPMax
            # if the requested power and requested energy are available, discharge at the requested power
            elif genP[idx] < essPMax and essETot > genP[idx]/4:
                essETot = essETot - (genP[idx]/4)
                essP[idx] = genP[idx]
                essEOut[idx] = essEOut[idx - 1] + (genP[idx] / 4)
                expendedWtgE = expendedWtgE + (genP[idx]/4)
                genPNew[idx] = genPNew[idx] - genP[idx]
            # if the requested power exceeds the max battery power, and the requested energy is not available, discharge at constant rate to 0 kWh
            elif (genP[idx] > essPMax and essETot < essPMax/4) or (genP[idx] < essPMax and essETot < genP[idx]/4):
                essEOut[idx] = essEOut[idx - 1] + essETot
                essP[idx] = 4*essETot
                expendedWtgE = expendedWtgE + essETot
                essETot = 0
            # if the requested power is available, but the
            #else:

            #if totLoadP[idx] - wtgP[idx] - essP[idx] - genPNew[idx] < 0:
               # print('Power balance :', totLoadP[idx] - wtgP[idx] - essP[idx] - genPNew[idx], totLoadP[idx], wtgP[idx], genPNew[idx])
    print('*****************************************')
    print('GBS Energy capacity [kWh]: ', essEMax)
    print('ESS cummulative kWh in: ', recoveredWtgE)
    print('ESS cummulative kWh out: ', expendedWtgE)
    wpRec[j] = recoveredWtgE

    # Re-calc diesel power
    genPReduced = np.copy(genP)
    essPOut = np.copy(essP)
    essPOut[essP < 0] = 0
    essPIn = np.copy(essP)
    essPIn[essP > 0] = 0
    essPIn = -essPIn
    genPReduced = genP - essPOut
    genPAvailRed, runHoursRed = dieselSchedule(genPReduced, MOL, srcP, gen1PMax, gen2PMax, gen3PMax)
    #print('Net ESS power: ', np.sum(essP)/4)
    #plt.plot(mpTimeStamps, essP)#totLoadP - wtgP - genP + curtailedP)
    #plt.plot(mpTimeStamps, genPReduced)
    print('Reduced diesel kWh: ', np.nansum(genP - genPReduced)/4)
    dieselEReductionArray[j] = np.nansum(genP - genPReduced)/4
    print('Reduced diesel run hours: ', runHoursRed)
    runHoursArray[j] = runHoursRed
    print('Number of diesel configuration changes : ', np.sum(np.abs(np.sign(np.diff(genPAvailRed)))))
    configChngTot[j] = np.sum(np.abs(np.sign(np.diff(genPAvailRed))))
    nonZeroGenPAvailRed = np.copy(genPAvailRed)
    nonZeroGenPAvailRed[genPAvailRed == 0] = np.nan
    print('Diesel capacity factor : ', np.nanmean(genPReduced/nonZeroGenPAvailRed))
    genCapFac[j] = np.nanmean(genPReduced/nonZeroGenPAvailRed)
    print('Max. ESS P: ', np.nanmax(np.abs(essP)))
    essPMaxAct[j] = np.nanmax(np.abs(essP))
    #print('Net power balance :', np.nansum(totLoadP - wtgP - essP - genPReduced))

print((np.nansum(curtailedP) / 4) / (np.nansum(wtgP) / 4), (np.nansum(wtgP) / 4))
'''#plt.plot(mpTimeStamps, genP)
#plt.plot(mpTimeStamps, genPAvail)
#plt.plot(mpTimeStamps, curtailedP)
plt.plot(mpTimeStamps, genP)
plt.plot(mpTimeStamps, essP)
plt.plot(essEMaxRng, wpRec/1000)
plt.show()

curtailedP[np.isnan(curtailedP)] = 0
curtailedPD = (curtailedP[1:] - curtailedP[:-1])/4

sns.distplot(curtailedPD)'''

# create total load time series for the powerhouse
# if gen1P.size == gen2P.size:
#     genP = gen1P + gen2P
#     print('Same size')
# else:
#     print('Size mismatch gen1P and gen2P.')
#
# if genP.size == gen3P.size:
#     genP = genP + gen3P
#
# # produce distribution of differences
# dt = time1[1:] - time1[:-1]
# meanDt = np.mean(dt)
# dGenP = genP[1:] - genP[:-1]

# sns.distplot(dt)

plt.subplots_adjust(bottom=0.2)
plt.xticks(rotation=25)
ax = plt.gca()
xfmt = md.DateFormatter('%Y-%m-%d')
ax.xaxis.set_major_formatter(xfmt)
#plt.plot(mpTimeStamps, mtvLoadP)
#plt.plot(mpTimeStamps, stmLoadP)
#plt.plot(mpTimeStamps, totLoadP)
#plt.plot(mpTimeStamps, wtgP)
#plt.plot(mpTimeStamps, genP)
#plt.hist(genP, bins='auto')
#plt.savefig('StMarysAndMtVillageLoad.eps', format = 'eps')
#plt.plot(mpTimeStamps, genP, label='GenP')
#plt.plot(mpTimeStamps, curtailedP, label= 'CurtailedP')
#plt.legend(loc='upper right')
#plt.xlabel('GBS Energy Capacity [kWh]')
#plt.ylabel('Total diesel fleet configuration changes [#/yr]')

# SRC Figure
plt.xlabel('Time')
plt.ylabel('Spinning reserve capacity required [kW]')
plt.plot(mpTimeStamps, srcP + genP)
#plt.plot(mpTimeStamps, totLoadP)
plt.plot(mpTimeStamps, genPAvail)

#plt.semilogx(essEMaxRng, wpRec/1000)
'''
vals, base = np.histogram(genDP, bins=150)
cumDist = np.cumsum(vals)
plt.plot(base[:-1], cumDist/genP.size)'''
#plt.savefig('baselineSRC.eps', format = 'eps')
plt.show()

'''
# Demo plot of loss of wind farm scenario
time = np.array(range(120))
wtgP = np.zeros(time.size, 'int')
wtgP[:15] = 100
essP = np.zeros(time.size, 'int')
x = 100 - 2 * np.array(range(15))
essP[15:30] = x
essP[30:40] = essP[29]
essP[40:60] = essP[39] - 35
x1 = essP[59] - (essP[59]/15)*np.array(range(15))
essP[60:75] = x1
genP = np.zeros(time.size, 'int')
genP[:15] = 120
genP[15:30] = 120 - x + 100
genP[30:60] = genP[29]
genP[60:75] = genP[59] - x1 + essP[59]
genP[75:] = genP[74]

plt.figure(figsize=(8, 5.5))
plt.plot(time, wtgP)
plt.plot(time, essP)
plt.plot(time, genP)
plt.savefig('lossOfWindDemo.eps', format = 'eps')
plt.show()'''

'''Helper routines go down here'''


# hexaspace computer graphics 09
# cube array myOrtho myLookAt
##################################################
import glfw
from OpenGL.GL import *
from OpenGL.GLU import *
import numpy as np

# draw cube glVertex3f quads*4 / 4각형 6개로 육면제 그리기
def drawUnitCube():
    glBegin(GL_QUADS)
    glVertex3f( 0.5, 0.5,-0.5)
    glVertex3f(-0.5, 0.5,-0.5)
    glVertex3f(-0.5, 0.5, 0.5)
    glVertex3f( 0.5, 0.5, 0.5)

    glVertex3f( 0.5,-0.5, 0.5)
    glVertex3f(-0.5,-0.5, 0.5)
    glVertex3f(-0.5,-0.5,-0.5)
    glVertex3f( 0.5,-0.5,-0.5)

    glVertex3f( 0.5, 0.5, 0.5)
    glVertex3f(-0.5, 0.5, 0.5)
    glVertex3f(-0.5,-0.5, 0.5)
    glVertex3f( 0.5,-0.5, 0.5)

    glVertex3f( 0.5,-0.5,-0.5)
    glVertex3f(-0.5,-0.5,-0.5)
    glVertex3f(-0.5, 0.5,-0.5)
    glVertex3f( 0.5, 0.5,-0.5)

    glVertex3f(-0.5, 0.5, 0.5)
    glVertex3f(-0.5, 0.5,-0.5)
    glVertex3f(-0.5,-0.5,-0.5)
    glVertex3f(-0.5,-0.5, 0.5)

    glVertex3f( 0.5, 0.5,-0.5)
    glVertex3f( 0.5, 0.5, 0.5)
    glVertex3f( 0.5,-0.5, 0.5)
    glVertex3f( 0.5,-0.5,-0.5)
    glEnd()

# glTranslatef와 glScalef 를 push pop사이에 함으로써 현재행렬에 영향을 주지 않는다.
def drawCubeArray():
    for i in range(5):
        for j in range(5):
            for k in range(5):
                glPushMatrix()
                glTranslatef(i,j,-k-1)
                glScalef(.5,.5,.5)
                drawUnitCube()
                glPopMatrix()

# xyz축
def drawFrame():
    glBegin(GL_LINES)
    glColor3ub(255, 0, 0)
    glVertex3fv(np.array([0.,0.,0.]))
    glVertex3fv(np.array([1.,0.,0.]))
    glColor3ub(0, 255, 0)
    glVertex3fv(np.array([0.,0.,0.]))
    glVertex3fv(np.array([0.,1.,0.]))
    glColor3ub(0, 0, 255)
    glVertex3fv(np.array([0.,0.,0]))
    glVertex3fv(np.array([0.,0.,1.]))
    glEnd()

def render():
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
    glEnable(GL_DEPTH_TEST)
    glPolygonMode( GL_FRONT_AND_BACK, GL_LINE )
    glLoadIdentity()

    myOrtho(-5,5,-5,5,-8,8)
    myLookAt(np.array([5,3,5]), np.array([1,1,-1]),np.array([0,1,0]))

    #Above two lines must behaves exactly same as the below two lines
    #위의 두 함수는 아래 두개와 같은 작동을 한다
    #glOrtho(-5,5,-5,5,-8,8)
    #gluLookAt(5,3,5 , 1,1,-1, 0,1,0)

    # xyz축 그리기
    drawFrame()
    # 흰색 큐브배열 그리기
    glColor3ub(255, 255, 255)
    drawCubeArray()

# glOrtho와 같은 작동을 하게 하는 함수
def myOrtho(left, right, bottom, top, near, far):                           # scale      translate
    moth=np.array([[2/(right-left),0,0,-(right+left)/(right-left)],         # lr평균 0 0 평균차이
                  [0, 2/(top-bottom),0,-(top+bottom)/(top-bottom)],         # 0 tb평균 0 평균차이
                  [0, 0, -2 / (far - near), -(far + near) / (far - near)],  # 0 0 fn평균 평균차이
                  [0, 0, 0, 1]])
    moth=moth.T   #glMultMatrixf는 .T역행렬을 해줘야한다.
    # 현재 행렬에 moth를 곱한다.
    glMultMatrixf(moth)

# eye point, look-at point, up vector --->> 카메라의 coordinates 백터 uvw, origin point(=eye)
def myLookAt(eye, at, up):
    w=(eye-at)/np.sqrt(np.dot(eye-at,eye-at))   # w는 물체->카메라위치의 unit 백터이다
    u=np.cross(up,w)/np.sqrt(np.dot(np.cross(up,w),np.cross(up,w))) #u는 w와 up을 외적(수직한 백터)의 unit
    v=np.cross(w,u) # v는 wu의 외적하여 unit함
    mlook=np.identity(4)    # global frame(object space)->local(world space)로 변환하는 matrix를 만드는 방법은
                            # m=[U,V,W,ORIGIN P]이다.
                            # 반대인 view space<-world space는 m^(-1) = [u1,u2,u3,-U`p1]이다.
                                                                    #  [v1,v2,v3,-V`p2]
                                                                    #  [w1,w2,w3,-W`p3]
    mlook[0, 0:3] = u
    mlook[1, 0:3] = v
    mlook[2, 0:3] = w
    mlook[0, 3] = -np.dot(u,eye)
    mlook[1, 3] = -np.dot(v, eye)
    mlook[2, 3] = -np.dot(w, eye)
    mlook=mlook.T   #glMultMatrixf는 .T역행렬을 해줘야한다.
    # 현재 행렬에 mlook을 곱한다.
    glMultMatrixf(mlook)

def main():
    if not glfw.init():
        return
    window = glfw.create_window(480,480,'cube array myOrtho myLookAt', None,None)
    if not window:
        glfw.terminate()
        return
    glfw.make_context_current(window)

    while not glfw.window_should_close(window):
        glfw.poll_events()
        render()
        glfw.swap_buffers(window)

    glfw.terminate()

if __name__ == "__main__":
    main()


from datetime import datetime
from fastapi import APIRouter, Depends
from fastapi.responses import JSONResponse
from .internal.weather import WeatherService
from .schemas.weather import WeatherRequest, WeatherResponse

router = APIRouter(
    prefix="/weather",
    tags=["weather"],
    responses={
        404: {"description": "Not found"},
    },
)

weather_service = WeatherService(
    api_app_keyname="weather", api_key_keyname="OPENWEATHERAPIKEY"
)

@router.get("/", response_model=WeatherResponse)
async def get_weather(params: WeatherRequest = Depends()):
    data, status_code = weather_service.get_weather(params.long, params.lat)
    if status_code == 200:
        message = f"Got data for {params.long}, {params.lat}"
        success = True
    else:
        message = data["message"]
        success = False
    return JSONResponse(
        {"data": data, "message": message, "success": success}, status_code
    )


import numpy as np
import cv2
import matplotlib.pyplot as plt
from skimage.color import rgb2gray
from skimage import data
from skimage.filters import gaussian
from skimage.segmentation import active_contour

#LBP is faster (a few times faster) but less accurate. (10-20% less than Haar).
# If you want to detect faces on an embedded system, I think LBP is the choice,
# because it does all the calculations in integers. Haar uses floats, which is a killer for embedded/mobile.

cap = cv2.VideoCapture(0)

# Create the haar or lbp cascade
#faceCascade = cv2.CascadeClassifier("data/haarcascade_frontalface_default.xml")
faceCascade = cv2.CascadeClassifier("data/lbpcascade_frontalface.xml")

while(True):
	# Capture frame-by-frame
	ret, frame = cap.read()

	# Our operations on the frame come here
	gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

	# Detect faces in the image
	faces = faceCascade.detectMultiScale(
		gray,
		scaleFactor=1.1,
		minNeighbors=5,
		minSize=(30, 30)
		#flags = cv2.CV_HAAR_SCALE_IMAGE
	)

	print("Found {0} faces!".format(len(faces)))

	# a=[]
	# b=[]
	s = np.linspace(0, 2*np.pi, 400)
	a = 190 + 150*np.cos(s)
	b = 320 + 150*np.sin(s)
	init = np.array([a, b]).T
	# Draw a rectangle around the faces
	for (x, y, w, h) in faces:
	    cv2.ellipse(frame,(x+w/2,y+h/2),(w,h),0,0,360,(0,255,0))
	    # a.append(x)
	    # b.append(y)
        # cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)
	# snake=active_contour(gaussian(frame, 3),
                       # init, alpha=0.015, beta=10, gamma=0.001)

	# Display the resulting frame
	cv2.imshow('frame', frame)
	if cv2.waitKey(1) & 0xFF == ord('a'):
		break

# When everything done, release the capture
cap.release()
cv2.destroyAllWindows()