from QuotaUtilities import *
import sys

def etfHolding():
    rdate=reportDate()
    all = ak.fund_em_etf_fund_daily()
    etf=all[all['类型']=='指数型-股票']
    etf.set_index('基金代码',inplace=True)
    df=pd.DataFrame()
    for k,v in tqdm(etf.iterrows(), total=etf.shape[0]):
        holding=ak.fund_em_portfolio_hold(code=k, year=rdate.year)
        if len(holding)==0:
            continue
        holding=holding[holding['季度']==holding['季度'].values[0]]
        holding['etf_code']=k
        holding['etf']=v['基金简称']
        etfK=ak.fund_em_etf_fund_info(fund=k)
        etfK['累计净值'] = pd.to_numeric(etfK['累计净值'], errors='coerce')
        holding['etf_ma20/60']=etfK['累计净值'][-20:].mean()/etfK['累计净值'][-60:].mean()
        df = df.append(holding)
    df.drop(labels=['序号'],axis=1,inplace=True)
    df.to_csv('etf.csv',index=False)

def dealFundCode(code:str):
    if code.startswith('5'):
        return 'SH'+code
    else:
        return 'SZ'+code

def etfStocks():
    etf=pd.read_csv('etf.csv',dtype={'股票代码':str,'etf_code':str})
    es=etf[etf['占净值比例']<0.1].copy()
    es=es.sort_values(by=['占净值比例'],ascending=False)
    es.drop_duplicates(subset='股票代码', keep='first',inplace=True)
    ah=ak.stock_hk_spot_em().set_index('代码')
    for k,v in es.iterrows():
        symbol=v['股票代码']
        if '.' in symbol:
            es.loc[k,'股票代码'] = symbol.split('.')[0]
        elif symbol[-5:] in ah.index and ah.loc[symbol[-5:],'名称']==v['股票名称']:
            es.loc[k,'股票代码'] = symbol[-5:]
        else:
            if symbol.startswith('6'):
                es.loc[k,'股票代码'] = 'SH'+symbol
            else:
                es.loc[k,'股票代码'] = 'SZ'+symbol
    es.set_index('股票代码', inplace=True)
    es['Percent']=None
    es['ma20/ma60'] = None
    xq_a_token = 'xq_a_token=' + requests.get("https://xueqiu.com", headers={"user-agent": "Mozilla"}).cookies['xq_a_token'] + ';'
    for k, v in tqdm(es.iterrows(), total=es.shape[0]):
        kk = getK(k, xq_a_token=xq_a_token)
        if len(kk)>0:
            cls=kk['close'].values
            es.loc[k, 'Percent'] = round(cls[-1]/cls[-2]-1,4)
            es.loc[k, 'ma20/ma60'] = round(cls[-20:].mean() / cls[-60:].mean(),4)
    es['股票名称'] = es.apply(lambda x: '{stock_name}'.format(
        stock_code=x.name, stock_name=x['股票名称']), axis=1)
    es['etf'] = es.apply(
        lambda x: '{fundname}'.format(fundcode=dealFundCode(x['etf_code']),fundname=x['etf']), axis=1)
    es=es[['股票名称','Percent','ma20/ma60','etf','占净值比例']]
    es.sort_values(by=['ma20/ma60'],ascending=False,inplace=True)
    renderHtml(es, '../CMS/source/Quant/etf.html', 'etf')

if __name__=='__main__':
    if len(sys.argv) > 1:
        etfHolding()
    etfStocks()


from helper_functions import choose_statistic, population_distribution, sampling_distribution
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy import stats
import seaborn as sns
import codecademylib3

# task 1: load in the spotify dataset
spotify_data = pd.read_csv("spotify_data.csv")
# task 2: preview the dataset
print(spotify_data.head())
# task 3: select the relevant column
song_tempos = spotify_data.tempo
# task 5: plot the population distribution with the mean labeled
population_distribution(song_tempos)
# task 6: sampling distribution of the sample mean
sampling_distribution(song_tempos, 30, "Mean")
# task 8: sampling distribution of the sample minimum
sampling_distribution(song_tempos, 30, "Minimum")
# task 10: sampling distribution of the sample variance
sampling_distribution(song_tempos, 30, "Variance")
# task 13: calculate the population mean and standard deviation
population_mean = choose_statistic(song_tempos, "Mean")
population_std = choose_statistic(song_tempos, "Standard Deviation")
print(population_mean, population_std)
# task 14: calculate the standard error
standard_error = (population_std)/(np.sqrt(30))
print(standard_error)
# task 15: calculate the probability of observing an average tempo of 140bpm or lower from a sample of 30 songs
less_140 = stats.norm.cdf(140, population_mean, standard_error)
print(less_140)
# task 16: calculate the probability of observing an average tempo of 150bpm or higher from a sample of 30 songs
more_than_150 = 1 - stats.norm.cdf(150, population_mean, standard_error)
print(more_than_150)
# EXTRA
def choose_statistic(x, sample_stat_text):
  # calculate mean if the text is "Mean"
  if sample_stat_text == "Mean":
    return np.mean(x)
  # calculate minimum if the text is "Minimum"
  elif sample_stat_text == "Minimum":
    return np.min(x)
  # calculate variance if the text is "Variance"
  elif sample_stat_text == "Variance":
    return np.var(x, ddof=1)
  # calcula o desvio padrão
  elif sample_stat_text == "Standard Deviation":
    return np.std(x)
  # calcula o mínimo ou máximo
  elif sample_stat_text == "Min":
    return np.min(x)
  elif sample_stat_text == "Max":
    return np.max(x)
  # raise error if sample_stat_text is not "Mean", "Minimum", or "Variance"
  else:
    raise Exception('Make sure to input "Mean", "Minimum", or "Variance"')


from neuron import gui, h
from neuron.units import ms, mV
import numpy as np
import time
import pandas as pd
import networkx as nx
from tqdm import tqdm
import warnings
import re
import random
from math import floor
import matplotlib.pyplot as plt
import seaborn as sns
import numba
from numba import jit
from scipy.ndimage import gaussian_filter1d
import glob
import os
from scipy.optimize import curve_fit
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
import threading
import multiprocessing

warnings.filterwarnings("ignore", category=np.VisibleDeprecationWarning)
warnings.filterwarnings("ignore", category=numba.NumbaDeprecationWarning)
warnings.simplefilter(action='ignore', category=FutureWarning) # remember update df.append to pd.concat

class CellWithNetworkx:
    def __init__(self, swc_file):
        h.load_file("import3d.hoc")

        # changing the loc of folder seems to make loading the relative path wrong
        current_directory = os.path.dirname(__file__)  # 如果在脚本中使用，__file__是指当前脚本的文件名
        os.chdir(current_directory)
        relative_path = './mod/nrnmech.dll'
        nrnmech_path = os.path.join(current_directory, relative_path)
        h.nrn_load_dll(nrnmech_path)

        h.load_file('./modelFile/L5PCbiophys3.hoc')
        h.load_file('./modelFile/L5PCtemplate.hoc')
        self.complex_cell = h.L5PCtemplate(swc_file)
        h.celsius = 37
        h.v_init = self.complex_cell.soma[0].e_pas
        self.G = None # undirected graph
        self.DiG = None # directed graph
        self.sp = None
        self.distance_matrix = None

        self.num_syn_basal_exc = 0
        self.num_syn_apic_exc = 0
        self.num_syn_basal_inh = 0
        self.num_syn_apic_inh = 0
        self.num_syn_clustered = 0

        # we should have 2 rnd, the one for positioning should be fixed through the simu
        # while the one for generating spikes should be different for each simu
        self.rnd = np.random.RandomState(10)
        self.spike_interval = 2000/1 # interval=2000(ms)/f

        self.syn_param_exc = [0, 0.3, 1.8, 0.0016] # reverse_potential, tau1, tau2, syn_weight
        self.syn_param_inh = [-86, 1, 8, 0.0008]

        self.sections_basal = [i for i in map(list, list(self.complex_cell.basal))]
        self.sections_apical = [i for i in map(list, list(self.complex_cell.apical))]
        self.all_sections = [i for i in map(list, list(self.complex_cell.soma))] + self.sections_basal + self.sections_apical

        self.section_df = pd.DataFrame(columns=['parent_id',
                                                'section_id',
                                                'parent_name',
                                                'section_name',
                                                'length',
                                                'section_type'])

        self.section_synapse_df = pd.DataFrame(columns=['section_id_synapse',
                                                'section_synapse',
                                                'segment_synapse',
                                                'synapse',
                                                'netstim',
                                                'random',
                                                'netcon',
                                                'loc',
                                                'type',
                                                'cluster_id',
                                                'pre_unit_id',
                                                'region']) # for adding vecstim of different orientation

        self.spike_counts_basal_inh = None
        self.spike_counts_apic_inh = None

        self.time_interval = 1/2000 # 2ms, 0.002s
        self.FREQ_INH = 10  # Hz, /s
        self.DURATION = 2000

        # For clustered synapses

        # self.num_syn_clustered = None
        self.num_clusters = None
        self.cluster_radius = None
        self.distance_to_soma = None
        self.num_conn_per_preunit = None

        self.ori_dg_list = [0.0, 45.0, 90.0, 135.0, 180.0, 225.0, 270.0, 315.0]
        self.pref_ori_dg = None

        self.unit_ids = None
        self.indices = None

        # For tuning curve
        # self.num_spikes_list = []
        self.num_spikes_df = None

        self.lock = None

        self.type_array = None

        self.class_dict = None
        self._create_graph()
        self._set_graph_order()

    # @jit(forceobj=True)
    def add_synapses(self, num_syn_basal_exc, num_syn_apic_exc, num_syn_basal_inh, num_syn_apic_inh):
        self.num_syn_basal_exc = num_syn_basal_exc
        self.num_syn_apic_exc = num_syn_apic_exc
        self.num_syn_basal_inh = num_syn_basal_inh
        self.num_syn_apic_inh = num_syn_apic_inh

        # add excitatory synapses
        self._add_single_synapses(num_syn_basal_exc, 'basal', 'exc')
        self._add_single_synapses(num_syn_apic_exc, 'apical', 'exc')

        # add inhibitory synapses
        self._add_single_synapses(num_syn_basal_inh, 'basal', 'inh')
        self._add_single_synapses(num_syn_apic_inh, 'apical', 'inh')

    def assign_clustered_synapses(self, num_clusters, cluster_radius, distance_to_soma, num_conn_per_preunit=3):
        indices = self._generate_indices(num_clusters, num_conn_per_preunit)
        self.indices = indices

        self.num_clusters = num_clusters
        self.cluster_radius = cluster_radius
        self.distance_to_soma = distance_to_soma
        self.num_conn_per_preunit = num_conn_per_preunit

        # sec_syn_bg_exc_df = self.section_synapse_df[self.section_synapse_df['type'] == 'A']

        dist_list = self.class_dict.get(distance_to_soma, [])
        # sections directly connected to soma
        sections_ordered = [section[0].sec for i, section in enumerate(self.all_sections) if i in dist_list]

        for i in range(num_clusters):
            sec_syn_bg_exc_df = self.section_synapse_df[self.section_synapse_df['type'] == 'A']

            sec_syn_bg_exc_ordered_df = self.section_synapse_df[
                (self.section_synapse_df['section_synapse'].isin(sections_ordered)) &
                (self.section_synapse_df['type'] == 'A')]

            index_list = indices[i]
            # use the rnd for positioning
            num_syn_per_cluster = len(index_list)
            syn_ctr = sec_syn_bg_exc_ordered_df.loc[self.rnd.choice(sec_syn_bg_exc_ordered_df.index)]

            # assign the center as clustered synapse
            self.section_synapse_df.loc[syn_ctr.name, 'type'] = 'C'
            self.section_synapse_df.loc[syn_ctr.name, 'cluster_id'] = i
            self.section_synapse_df.loc[syn_ctr.name, 'pre_unit_id'] = index_list[0]

            syn_ctr_sec = syn_ctr['section_synapse']
            syn_surround_ctr = sec_syn_bg_exc_ordered_df[
                (sec_syn_bg_exc_ordered_df['section_synapse'] == syn_ctr_sec) &
                (sec_syn_bg_exc_ordered_df.index != syn_ctr.name)]

            dis_syn_from_ctr = np.array(np.abs(syn_ctr['loc'] - syn_surround_ctr['loc']) * syn_ctr_sec.L)
            # use exponential distribution to generate loc
            dis_mark_from_ctr = np.sort(self.rnd.exponential(cluster_radius, num_syn_per_cluster - 1))

            # not enough synapses on the same section
            syn_ctr_sec_id = syn_ctr['section_id_synapse']
            syn_suc_sec_id = syn_ctr_sec_id
            syn_pre_sec_id = syn_ctr_sec_id
            while len(dis_syn_from_ctr) < num_syn_per_cluster - 1:
                # the children section of the center section
                if list(self.DiG.successors(syn_ctr_sec_id)) != []:
                    # iterate
                    syn_suc_sec_id = self.rnd.choice(list(self.DiG.successors(syn_suc_sec_id)))
                    syn_suc_sec = sec_syn_bg_exc_df[sec_syn_bg_exc_df['section_id_synapse'] == syn_suc_sec_id]['section_synapse'].values[0]
                    syn_suc_surround_ctr = sec_syn_bg_exc_df[sec_syn_bg_exc_df['section_id_synapse'] == syn_suc_sec_id]
                    dis_syn_suc_from_ctr = np.array((1 - syn_ctr['loc']) * syn_ctr_sec.L + syn_suc_surround_ctr['loc'] * syn_suc_sec.L)

                # the parent section of the center section
                # there is no section on the soma, so we should not choose soma as the parent section
                if list(self.DiG.predecessors(syn_ctr_sec_id)) not in ([], [0]):
                    syn_pre_sec_id = self.rnd.choice(list(self.DiG.predecessors(syn_pre_sec_id)))
                    syn_pre_sec = sec_syn_bg_exc_df[sec_syn_bg_exc_df['section_id_synapse'] == syn_pre_sec_id]['section_synapse'].values[0]
                    syn_pre_surround_ctr = sec_syn_bg_exc_df[sec_syn_bg_exc_df['section_id_synapse'] == syn_pre_sec_id]
                    dis_syn_pre_from_ctr = np.array(syn_ctr['loc'] * syn_ctr_sec.L + (1 - syn_pre_surround_ctr['loc']) * syn_pre_sec.L)

                dis_syn_from_ctr = np.concatenate((dis_syn_from_ctr, dis_syn_suc_from_ctr, dis_syn_pre_from_ctr))
                syn_surround_ctr = pd.concat([syn_surround_ctr, syn_suc_surround_ctr, syn_pre_surround_ctr])

            cluster_member_index = self._distance_synapse_mark_compare(dis_syn_from_ctr, dis_mark_from_ctr)
            # assign the surround as clustered synapse
            self.section_synapse_df.loc[syn_surround_ctr.iloc[cluster_member_index].index, 'type'] = 'C'
            self.section_synapse_df.loc[syn_surround_ctr.iloc[cluster_member_index].index, 'cluster_id'] = i
            self.section_synapse_df.loc[syn_surround_ctr.iloc[cluster_member_index].index, 'pre_unit_id'] = index_list[1:]

            print('cluster_id: ', i)
            print(self.section_synapse_df[self.section_synapse_df['cluster_id'] == i]['segment_synapse'].values)

    def visualize_synapses(self, folder_path, title='Synapses'):
        s = h.PlotShape(False)
        self._recursive_plot(s, self.section_synapse_df['segment_synapse'].values)
        plt.title(title)

        file_path = os.path.join(folder_path, f'figure_synapses.png')
        plt.savefig(file_path)
        plt.close()

    # @jit(forceobj=True)
    def add_inputs(self, folder_path):

        ori_dg_list, unit_ids, num_stims = self.ori_dg_list, self.unit_ids, 2
        # 创建一个空的 DataFrame
        self.num_spikes_df = pd.DataFrame(index=range(1, num_stims + 1), columns=ori_dg_list)
        self.lock = threading.Lock()
        # with ThreadPoolExecutor(max_workers=multiprocessing.cpu_count()) as executor:
        #     # futures = []
        #     # for ori_dg in ori_dg_list:
        #     #     futures.append(executor.submit(self._process_ori_dg, ori_dg, unit_ids, num_stims, folder_path))
        #     futures = [executor.submit(self._process_ori_dg, ori_dg, unit_ids, num_stims, folder_path) for ori_dg in ori_dg_list]
        #     # futures = [executor.submit(self._process_ori_dg, ori_dg_list, unit_ids, num_stims, folder_path)]

        #     for future in futures:
        #         future.result()

        # self.num_spikes_df.to_csv('./num_spikes_df_parallel.csv', encoding='utf-8', index=False)

        # self._add_background_exc_inputs()
        # self._add_background_inh_inputs()
        for ori_dg in ori_dg_list:
            # stim_ids vary across different orientations
            stim_ids = self._get_stim_ids(ori_dg)

            for stim_id in stim_ids[:num_stims]:
                spt_unit_list = self._create_vecstim(ori_dg, stim_id, unit_ids)
                self._add_background_exc_inputs()
                self._add_clustered_inputs(spt_unit_list)
                self._add_background_inh_inputs()

                stim_index = np.where(stim_ids == stim_id)[0][0] + 1
                self._run_simulation(ori_dg, stim_id, stim_index)

                for i in plt.get_fignums():
                    plt.figure(i)
                    file_path = os.path.join(folder_path, f'figure_{ori_dg}_{stim_id}_{i}.png')
                    plt.savefig(file_path)
                    plt.close(i)

        self.num_spikes_df.to_csv('./num_spikes_df_oarallel.csv', encoding='utf-8', index=False)

    def _process_ori_dg(self, ori_dg, unit_ids, num_stims, folder_path):

        # for ori_dg in ori_dg_list:
        stim_ids = self._get_stim_ids(ori_dg)
        for stim_id in stim_ids[:num_stims]:
            spt_unit_list = self._create_vecstim(ori_dg, stim_id, unit_ids)
            self._add_background_exc_inputs()
            self._add_clustered_inputs(spt_unit_list)
            self._add_background_inh_inputs()

            stim_index = np.where(stim_ids == stim_id)[0][0] + 1
            self._run_simulation(ori_dg, stim_id, stim_index)

            with threading.Lock():
                for i in plt.get_fignums():
                    plt.figure(i)
                    file_path = os.path.join(folder_path, f'figure_{ori_dg}_{stim_id}_{i}.png')
                    plt.savefig(file_path)
                    plt.close(i)

    def visualize_tuning_curve(self):
        # num_spikes = self.num_spikes_list
        # pref_ori_dg = self.pref_ori_dg

        # x_values = np.array(ori_dg_list)
        # y_values = np.array(num_spikes)

        # # Fit Gaussian curve
        # # params = self.fit_gaussian(x_values, y_values)

        # # Plot original data points
        # plt.figure(figsize=(5, 5))
        # plt.plot(x_values, y_values, label='Data Points')

        # # plt.scatter(x_values, y_values, label='Data Points')

        # # Plot Gaussian curve using the fitted parameters
        # # curve_x = np.linspace(min(x_values), max(x_values), 100)
        # # curve_y = self.gaussian_function(curve_x, *params)
        # # plt.plot(curve_x, curve_y, label='Gaussian Fit', color='red')

        # # # Mark the data points on the graph
        # # for i, txt in enumerate(num_spikes):
        # #     plt.annotate(txt, (x_values[i], y_values[i]), textcoords="offset points", xytext=(0, 5), ha='center')

        # # Display the plot

        # plt.figure(figsize=(5, 5))
        # plt.xlabel('Orientation (degrees)')
        # plt.ylabel('Number of Spikes')
        # plt.legend()

        # 假设有八个角度和每个角度对应的15个spike个数的值
        ori_dg_list = [0.0, 45.0, 90.0, 135.0, 180.0, 225.0, 270.0, 315.0]
        # spike_counts = np.random.randint(10, 20, size=(8, 15))  # 示例中使用随机生成的数据
        spike_counts = np.array(self.num_spikes_df)
        # 计算每个角度的平均值和标准差
        mean_spike_counts = np.mean(spike_counts, axis=1)
        std_dev_spike_counts = np.std(spike_counts, axis=1)

        # 绘制调谐曲线
        plt.errorbar(ori_dg_list, mean_spike_counts, yerr=std_dev_spike_counts, fmt='o-', capsize=5, label='Tuning Curve')

        # 在图上标出15个值对应的散点
        for ori_dg, spike_count_values in zip(ori_dg_list, spike_counts):
            plt.scatter([ori_dg] * len(spike_count_values), spike_count_values, color='gray', alpha=0.5)

        # 添加标签和标题
        plt.xlabel('Orientation (degrees)')
        plt.ylabel('Average Spike Count')
        plt.title('Tuning Curve with Scatter Points')
        plt.legend()

    def _run_simulation(self, ori_dg, stim_id, stim_index):
        soma_v = h.Vector().record(self.complex_cell.soma[0](0.5)._ref_v)
        dend_v = h.Vector().record(self.complex_cell.dend[0](0.5)._ref_v)
        apic_v = h.Vector().record(self.complex_cell.apic[0](0.5)._ref_v)
        time_v = h.Vector().record(h._ref_t)

        netcons_list = self.section_synapse_df['netcon']
        spike_times = [h.Vector() for _ in netcons_list]

        clustered_syn_index = self.section_synapse_df[self.section_synapse_df['type'] == 'C'].index
        spike_times_clustered = [spike_times[i] for i in clustered_syn_index]

        for nc, spike_times_vec in zip(netcons_list, spike_times):
            nc.record(spike_times_vec)

        h.tstop = self.DURATION
        st = time.time()
        h.run()
        print('complex cell simulation time {:.4f}'.format(time.time()-st))

        # 累加多个spike trains得到总的放电次数
        total_spikes = np.zeros(self.DURATION)  # 初始化总spike数数组

        for spike_times_vec in spike_times:
            try:
                total_spikes[np.floor(spike_times_vec).astype(int)] += 1 # 向下取整
            except ValueError:
                continue

        # 计算每个时间点的平均firing rate (Hz, /s)
        firing_rates = total_spikes / (np.mean(total_spikes) * self.time_interval)

        # threshold to define spikes
        threshold = 0

        num_spikes = self._count_spikes(soma_v, threshold)
        print(str(ori_dg)+'-'+str(stim_id))
        print("Number of spikes:", num_spikes)

        self.num_spikes_df.at[stim_index, ori_dg] = num_spikes
        # self.num_spikes_list.append(num_spikes)

        visualization_params = [ori_dg, stim_id, soma_v, dend_v, apic_v, time_v, spike_times_clustered]
        self._visualize_simulation(visualization_params)

    def _create_graph(self):
        all_sections = self.all_sections

        max_string_length = 50

        parent_list, parent_index_list = [], []

        for i, section_segment_list in enumerate(all_sections):
            section = section_segment_list[0].sec
            section_id = i
            section_name = section.psection()['name']
            match = re.search(r'\.(.*?)\[', section_name)
            section_type = match.group(1)
            L = section.psection()['morphology']['L']

            parent_list.append(section_name)
            parent_index_list.append(section_id)

            if i == 0:
                parent_name = 'None'
                parent_id = 0

            else:
                parent = section.psection()['morphology']['parent'].sec
                parent_name = parent.psection()['name']
                parent_id = parent_index_list[parent_list.index(parent_name)]

            # create data
            data_to_append = {'parent_id': parent_id,
                    'section_id': section_id,
                    'parent_name': parent_name,
                    'section_name': section_name,
                    'length': L,
                    'section_type': section_type}

            # self.section_df = self.section_df.append(data_to_append, ignore_index=True)
            self.section_df = pd.concat([self.section_df, pd.DataFrame(data_to_append, index=[0])], ignore_index=True)

        self.section_df.to_csv("cell1.csv", encoding='utf-8', index=False)
        Data = open('cell1.csv', "r")
        next(Data, None)  # skip the first line in the input file

        Graphtype = nx.Graph()
        DiGraphtype = nx.DiGraph()
        self.G = nx.parse_edgelist(Data, delimiter=',', create_using=Graphtype,
                                  nodetype=int, data=(('parent_name', str), ('section_name', str),
                                                      ('length', float), ('section_type', str)))
        Data = open('cell1.csv', "r")
        next(Data, None)  # skip the first line in the input file
        self.DiG = nx.parse_edgelist(Data, delimiter=',', create_using=DiGraphtype,
                                     nodetype=int, data=(('parent_name', str), ('section_name', str),
                                                      ('length', float), ('section_type', str)))
        self.sp = dict(nx.all_pairs_shortest_path(self.G))

    def _set_graph_order(self):
        order_dict = nx.single_source_shortest_path_length(self.G, 0)

        # 创建一个空字典来保存分类结果
        self.class_dict = {}

        # 将每个点根据距离分类
        for node, order in order_dict.items():
            if order not in self.class_dict:
                self.class_dict[order] = []
            self.class_dict[order].append(node)

        # 获取最远的点到soma的距离（k值）
        max_order = max(order_dict.values())

        # 输出分类结果
        # for i in range(max_order + 1):
        #     print(f"Class {i}: {self.class_dict.get(i, [])}")

    def _add_single_synapses(self, num_syn, region, sim_type):
        sections = self.sections_basal if region == 'basal' else self.sections_apical
        e_syn, tau1, tau2, syn_weight = self.syn_param_exc if sim_type == 'exc' else self.syn_param_inh
        type = 'A' if sim_type == 'exc' else 'B'

        if region == 'basal':
            section_length = np.array(self.section_df.loc[self.section_df['section_type'] == 'dend', 'length'])
        else:
            section_length = np.array(self.section_df.loc[self.section_df['section_type'] == 'apic', 'length'])

        def generate_synapse(_):
            section = random.choices(sections, weights=section_length)[0][0].sec
            section_name = section.psection()['name']

            section_id_synapse = self.section_df.loc[self.section_df['section_name'] == section_name, 'section_id'].values[0]

            loc = self.rnd.uniform()
            segment_synapse = section(loc)

            synapse = h.Exp2Syn(segment_synapse)
            synapse.e = e_syn
            synapse.tau1 = tau1
            synapse.tau2 = tau2

            data_to_append = {'section_id_synapse': section_id_synapse,
                            'section_synapse': section,
                            'segment_synapse': segment_synapse,
                            'synapse': synapse,
                            'netstim': None,
                            'random': None,
                            'netcon': None,
                            'loc': loc,
                            'type': type,
                            'cluster_id': -1,
                            'pre_unit_id': -1,
                            'region': region}

            # with threading.Lock():
            with self.lock:
                self.section_synapse_df = self.section_synapse_df.append(data_to_append, ignore_index=True)

        with ThreadPoolExecutor(max_workers=multiprocessing.cpu_count()) as executor:
            list(tqdm(executor.map(generate_synapse, range(num_syn)), total=num_syn))

        # for _ in tqdm(range(num_syn)):
        # # with tqdm(total=num_syn) as pbar:
        #     # i = 0
        #     # while i < num_syn:
        #     # section = self.rnd.choice(sections)[0].sec
        #     section = random.choices(sections, weights=section_length)[0][0].sec
        #     section_name = section.psection()['name']

        #     # Control the synpase density
        #     # max_num_syn = self.section_df.loc[self.section_df['section_name'] == section_name, 'max_num_syn'].values[0]
        #     # added_num_syn = self.section_df.loc[self.section_df['section_name'] == section_name, 'added_num_syn'].values[0]

        #     # if added_num_syn < max_num_syn:
        #     #     self.section_df.loc[self.section_df['section_name'] == section_name, 'added_num_syn'] += 1
        #     #     i += 1
        #     # else:
        #     #     continue

        #     section_id_synapse = self.section_df.loc[self.section_df['section_name'] == section_name, 'section_id'].values[0]

        #     loc = self.rnd.uniform()
        #     segment_synapse = section(loc)

        #     synapse = h.Exp2Syn(segment_synapse)
        #     synapse.e = e_syn
        #     synapse.tau1 = tau1
        #     synapse.tau2 = tau2

        #     data_to_append = {'section_id_synapse': section_id_synapse,
        #                                 'section_synapse': section,
        #                                 'segment_synapse': segment_synapse,
        #                                 'synapse': synapse,
        #                                 'netstim': None,
        #                                 'random': None,
        #                                 'netcon': None,
        #                                 'loc': loc,
        #                                 'type': type,
        #                                 'cluster_id': -1,
        #                                 'pre_unit_id': -1,
        #                                 'region': region}

        #     self.section_synapse_df = self.section_synapse_df.append(data_to_append, ignore_index=True)
        #     # self.section_synapse_df = pd.concat([self.section_synapse_df, pd.DataFrame(data_to_append, index=[0])], ignore_index=True)

        #     # pbar.update(1)
        #     time.sleep(0.01)

    def _distance_synapse_mark_compare(self, dis_syn_from_ctr, dis_mark_from_ctr):
        # 创建一个包含原始索引的列表
        original_indices = list(range(len(dis_syn_from_ctr)))
        index = []

        for value in dis_mark_from_ctr:
            # 计算与value差值最小的元素的索引
            min_index = min(original_indices, key=lambda i: abs(dis_syn_from_ctr[i] - value))
            # 将该索引加入结果列表，并从original_indices中移除
            index.append(min_index)
            original_indices.remove(min_index)

        return index

    def _generate_indices(self, num_clusters, num_conn_per_preunit=3):

        spt_path = 'C:/Users/Windows/Desktop/MIMOlab/Codes/AllenAtlas/results/spt_df'
        pref_ori_dg = 0
        self.pref_ori_dg = pref_ori_dg
        session_id = 732592105
        ori_dg = 0.0

        # for calculate the OSI
        spt_file = glob.glob(spt_path + f'/{pref_ori_dg}_{session_id}/spt_{ori_dg}.csv')
        file_path = spt_file[0] # usually only one file
        spt_df = pd.read_csv(file_path, index_col=None, header=0)

        # we need the presynaptic units always the same
        unit_ids = np.sort(spt_df['unit_id'].unique())
        self.unit_ids = unit_ids

        results = []  # 用于存储生成的列表
        indices = []
        for _ in range(len(unit_ids)):
            # choose 3 clusters without replacement
            sampled = self.rnd.choice(num_clusters, num_conn_per_preunit, replace=False)
            results.append(sampled)

        # 查找包含从0到k-1的列表的索引
        for i in range(num_clusters):
            index_list = [j for j, lst in enumerate(results) if i in lst]
            indices.append(index_list)

        return indices

    def _get_stim_ids(self, ori_dg):
        spt_path = 'C:/Users/Windows/Desktop/MIMOlab/Codes/AllenAtlas/results/spt_df'
        pref_ori_dg = 0
        self.pref_ori_dg = pref_ori_dg
        session_id = 732592105

        # for calculate the OSI
        spt_file = glob.glob(spt_path + f'/{pref_ori_dg}_{session_id}/spt_{ori_dg}.csv')
        file_path = spt_file[0] # usually only one file
        spt_df = pd.read_csv(file_path, index_col=None, header=0)

        # we need the presynaptic units always the same
        stim_ids = np.sort(spt_df['stimulus_presentation_id'].unique())
        print(f'stim_ids: {stim_ids}')

        return stim_ids

    def _add_background_exc_inputs(self):
        sec_syn_bg_exc_df = self.section_synapse_df[self.section_synapse_df['type'] == 'A']
        num_syn_background_exc = len(sec_syn_bg_exc_df)
        syn_weight = self.syn_param_exc[-1]

        def process_section(i):
            section = sec_syn_bg_exc_df.iloc[i]

            netstim = h.NetStim()
            netstim.interval = self.spike_interval
            netstim.number = 10
            netstim.start = 0
            netstim.noise = 1

            random = h.Random()
            random.Random123(i)
            random.negexp(1)
            netstim.noiseFromRandom(random)

            if section['netcon'] is not None:
                section['netcon'].weight[0] = 0

            synapse = section['synapse']
            netcon = h.NetCon(netstim, synapse)
            netcon.delay = 0
            netcon.weight[0] = syn_weight

            with self.lock:
                self.section_synapse_df.at[section.name, 'netstim'] = netstim
                self.section_synapse_df.at[section.name, 'random'] = random
                self.section_synapse_df.at[section.name, 'netcon'] = netcon

        with ThreadPoolExecutor(max_workers=multiprocessing.cpu_count()) as executor:
            list(tqdm(executor.map(process_section, range(num_syn_background_exc)), total=num_syn_background_exc))

        # for i in tqdm(range(num_syn_background_exc)):
        #     # need this change updated to the global dataframe
        #     section = sec_syn_bg_exc_df.iloc[i]

        #     netstim = h.NetStim()
        #     netstim.interval = self.spike_interval
        #     netstim.number = 10
        #     netstim.start = 0
        #     netstim.noise = 1

        #     random = h.Random()
        #     random.Random123(i)
        #     random.negexp(1)
        #     netstim.noiseFromRandom(random)

        #     ## turn off the old netcons
        #     if section['netcon'] is not None:
        #         section['netcon'].weight[0] = 0

        #     synapse = section['synapse']
        #     netcon = h.NetCon(netstim, synapse) # netstim is always from the same unit with diff orientation
        #     netcon.delay = 0
        #     netcon.weight[0] = syn_weight

        #     self.section_synapse_df.at[section.name, 'netstim'] = netstim
        #     self.section_synapse_df.at[section.name, 'random'] = random
        #     self.section_synapse_df.at[section.name, 'netcon'] = netcon

        #     time.sleep(0.01)

    def _add_background_inh_inputs(self):
        # sec_syn_exc_df = self.section_synapse_df[self.section_synapse_df['type'] == 'A' or self.section_synapse_df['type'] == 'C']
        exc_types = ['A', 'C']
        sec_syn_exc_df = self.section_synapse_df[self.section_synapse_df['type'].isin(exc_types)]

        exc_netcons_list = sec_syn_exc_df['netcon']
        syn_weight = self.syn_param_inh[-1]

        spike_times = [h.Vector() for _ in exc_netcons_list]
        for nc, spike_times_vec in zip(exc_netcons_list, spike_times):
            nc.record(spike_times_vec)

        h.tstop = self.DURATION
        st = time.time()
        h.run()
        print('complex cell simulation time {:.4f}'.format(time.time()-st))

        total_spikes = np.zeros(self.DURATION)  # 初始化总spike数数组

        for spike_times_vec in spike_times:
            try:
                total_spikes[np.floor(spike_times_vec).astype(int)] += 1 # 向下取整
            except ValueError:
                continue

        # 计算每个时间点的平均firing rate (Hz, /s)
        firing_rates = total_spikes / (np.mean(total_spikes) * self.time_interval)
        firing_rates_inh = firing_rates * self.FREQ_INH / np.mean(firing_rates)
        lambda_array = firing_rates_inh * self.time_interval

        self.spike_counts_basal_inh = np.random.poisson(lambda_array, size=(self.num_syn_basal_inh, self.DURATION))
        self.spike_counts_apic_inh = np.random.poisson(lambda_array, size=(self.num_syn_apic_inh, self.DURATION))

        sec_syn_bg_inh_df = self.section_synapse_df[self.section_synapse_df['type'] == 'B']

        def process_section(i):
            section = sec_syn_inh_df.iloc[i]

            counts = spike_counts_inh[i]
            spike_train = np.where(counts >= 1)[0] + 1000 * self.time_interval * np.random.rand(np.sum(counts >= 1))
            netstim = h.VecStim()
            netstim.play(h.Vector(spike_train))

            if section['netcon'] is not None:
                section['netcon'].weight[0] = 0

            synapse = section['synapse']
            netcon = h.NetCon(netstim, synapse)
            netcon.delay = 0
            netcon.weight[0] = syn_weight

            with self.lock:
                self.section_synapse_df.at[section.name, 'netstim'] = netstim
                self.section_synapse_df.at[section.name, 'netcon'] = netcon

        for region in ['basal', 'apical']:

            spike_counts_inh = self.spike_counts_basal_inh if region == 'basal' else self.spike_counts_apic_inh
            num_syn_background_inh = self.num_syn_basal_inh if region == 'basal' else self.num_syn_apic_inh
            sec_syn_inh_df = sec_syn_bg_inh_df[sec_syn_bg_inh_df['region'] == region]

            with ThreadPoolExecutor(max_workers=multiprocessing.cpu_count()) as executor:
                list(tqdm(executor.map(process_section, range(num_syn_background_inh)), total=num_syn_background_inh))

            # for i in tqdm(range(num_syn_background_inh)):
            #     section = sec_syn_inh_df.iloc[i]

            #     counts = spike_counts_inh[i]
            #     spike_train = np.where(counts>=1)[0] + 1000*self.time_interval*np.random.rand(np.sum(counts>=1))
            #     netstim = h.VecStim()
            #     netstim.play(h.Vector(spike_train))

            #     ## turn off the old netcons
            #     if section['netcon'] is not None:
            #         section['netcon'].weight[0] = 0

            #     synapse = section['synapse']
            #     netcon = h.NetCon(netstim, synapse) # netstim is always from the same unit with diff orientation
            #     netcon.delay = 0
            #     netcon.weight[0] = syn_weight

            #     self.section_synapse_df.at[section.name, 'netstim'] = netstim
            #     self.section_synapse_df.at[section.name, 'netcon'] = netcon

            #     time.sleep(0.01)

    def _add_clustered_inputs(self, spt_unit_list):
        sec_syn_clustered_df = self.section_synapse_df[self.section_synapse_df['type'] == 'C']
        num_syn_clustered = len(sec_syn_clustered_df)
        syn_weight = self.syn_param_exc[-1]

        for i in tqdm(range(num_syn_clustered)):
            # need this change updated to the global dataframe
            section = sec_syn_clustered_df.iloc[i]

            spt_unit = spt_unit_list[section['pre_unit_id']]
            # spt_unit_summed_list = spt_unit_summed_lists[section['cluster_id']]
            spt_unit_vector = h.Vector(spt_unit)
            netstim = h.VecStim()
            netstim.play(spt_unit_vector)

            ## turn off the old netcons
            if section['netcon'] is not None:
                section['netcon'].weight[0] = 0

            synapse = section['synapse']
            netcon = h.NetCon(netstim, synapse) # netstim is always from the same unit with diff orientation
            netcon.delay = 0
            netcon.weight[0] = syn_weight

            self.section_synapse_df.at[section.name, 'netstim'] = netstim
            self.section_synapse_df.at[section.name, 'netcon'] = netcon

            time.sleep(0.01)

        # self.visualize_simulation(1)

    def _create_vecstim(self, ori_dg, stim_id, unit_ids):
        # define the path of all csv files for spike trains
        spt_path = 'C:/Users/Windows/Desktop/MIMOlab/Codes/AllenAtlas/results/spt_df'

        # use glob.glob to extract the csv files with the orientation wanted
        # read every file in this folder
        pref_ori_dg = 0
        session_id = 732592105

        spt_file = glob.glob(spt_path + f'/{pref_ori_dg}_{session_id}/spt_{ori_dg}.csv')

        # folder_path = f'/{pref_ori_dg}_{session_id}/spt_{ori_dg}.csv'
        # spt_file = os.path.join(spt_path, folder_path)

        # for file_path in spt_file:
        file_path = spt_file[0] # usually only one file
        spt_df = pd.read_csv(file_path, index_col=None, header=0)

        ## Following part will differ across different ori_dg

        # not all units spike at each stimulus presentation,
        # we need choose the stim_id with the max number of units
        spt_grouped_df = spt_df.groupby(['unit_id', 'stimulus_presentation_id'])
        # max_stim_id = spt_df.groupby('stimulus_presentation_id')['unit_id'].nunique().idxmax()

        spt_unit_list = []
        ori_dg_list = [0.0, 45.0, 90.0, 135.0, 180.0, 225.0, 270.0, 315.0]
        for unit_id in unit_ids:
            try:
                spt_unit = spt_grouped_df.get_group((unit_id, stim_id))
                spt_unit = (spt_unit['spike_time'].values - spt_unit['spike_time'].values[0]) * 1000
            except KeyError:
                # for units not fired, add list of 0
                spt_unit = np.array([])

            spt_unit_list.append(spt_unit)

        return spt_unit_list

    def _visualize_simulation(self, visualization_params):
        ori_dg, stim_id, soma_v, dend_v, apic_v, time_v, spike_times_clustered = visualization_params

        # plotting the results
        plt.figure(figsize=(5, 5))
        plt.title(str(ori_dg) + '-' + str(stim_id))
        for i, spike_times_vec in enumerate(spike_times_clustered):
            try:
                if len(spike_times_vec) > 0:
                    plt.vlines(spike_times_vec, i + 0.5, i + 1.5)
            except IndexError:
                continue

        # 绘制firing rate曲线
        # plt.figure(figsize=(5, 5))
        # plt.plot(firing_rates,color='blue',label='Backgournd Excitatory Firing Rate')
        # plt.legend()
        # plt.xlabel('Time(ms)')
        # plt.ylabel('Firing Rate(Hz)')
        # plt.title('Firing Rate Curve')

        # 使用高斯核进行卷积，得到平滑的firing rate曲线 (don't consider currently)
        # sigma = 1  # 高斯核的标准差
        # smoothed_firing_rates = gaussian_filter1d(firing_rates, sigma)

        # plt.figure(figsize=(5, 5))
        # plt.plot(smoothed_firing_rates, label='Smoothed Firing Rate')
        # plt.legend()
        # plt.xlabel('Time(ms)')
        # plt.ylabel('Firing Rate(Hz)')
        # plt.title('Firing Rate Curve')

        plt.figure(figsize=(5, 5))
        plt.plot(time_v, soma_v, label='soma')
        plt.plot(time_v, dend_v, label='basal')
        plt.plot(time_v, apic_v, label='apical')
        plt.legend()
        plt.xlabel('Time (ms)')
        plt.ylabel('Voltage (mV)')
        plt.title(str(ori_dg) + '-' + str(stim_id))

    def _count_spikes(self, soma_voltage, threshold=0):
        spike_count = 0
        is_spiking = False

        for voltage in soma_voltage:
            if voltage > threshold and not is_spiking:
                is_spiking = True
                spike_count += 1
            elif voltage <= threshold:
                is_spiking = False

        return spike_count

    def calculate_cirvar(self, num_spikes, ori_dg_list):
        # 将角度转换为弧度
        theta_radians = np.deg2rad(ori_dg_list)

        # 计算 exp(2i * theta)
        complex_exp = np.exp(2j * theta_radians)

        # 计算加权和
        weighted_sum = np.sum(num_spikes * complex_exp) / np.sum(num_spikes)

        # 取模
        cirvar = round(np.abs(weighted_sum), 4)

        return cirvar

    def calculate_osi(self, num_spikes, ori_dg_list, pref_ori_dg):

        r_pref = num_spikes[ori_dg_list.index(pref_ori_dg)] + num_spikes[ori_dg_list.index((pref_ori_dg + 180) % 360)]
        r_ortho = num_spikes[ori_dg_list.index((pref_ori_dg + 90)) % 360] + num_spikes[ori_dg_list.index((pref_ori_dg + 270) % 360)]

        osi = round((r_pref - r_ortho) / (r_pref + r_ortho), 4)

        return osi

    def gaussian_function(self, x, amplitude, mean, stddev):
        return amplitude * np.exp(-((x - mean) / stddev) ** 2 / 2)

    def fit_gaussian(self, x, y):
        popt, _ = curve_fit(self.gaussian_function, x, y, p0=[1, np.mean(x), np.std(x)])
        return popt

    def _recursive_plot(self, s, seg_list, index=0):
        markers = {
            'A': 'or',  # 红色圆形
            'B': 'xb',  # 蓝色叉形
            'C': 'sg',  # 绿色方形
            'D': 'pk',  # 紫色五角星
            'E': 'dc',  # 青色钻石形
            'F': '^m',  # 品红色三角形
            'G': '*y',  # 黄色星型
            'H': '+k',  # 黑色十字形
        }

        if index == 0:
            return self._recursive_plot(s.plot(plt), seg_list, index+1)
        elif index <= len(seg_list):
            # if self.initialize_cluster_flag == False:
            segment_type = self.type_array[index - 1]
            marker = markers.get(segment_type, 'or')  # 如果类型不在字典中，默认使用'or'作为标记
            return self._recursive_plot(s.mark(seg_list[index - 1], marker), seg_list, index + 1)

                # if self.type_array[index-1] == 'A':
                #     return self._recursive_plot(s.mark(seg_list[index-1],'or'), seg_list, index+1)
                # else:
                #     return self._recursive_plot(s.mark(seg_list[index-1],'xb'), seg_list, index+1)
            # else:
            #     return self._recursive_plot(s.mark(seg_list[index-1],'xr'), seg_list, index+1)

    def add_clustered_synapses(self, num_syn_clustered=50, num_clusters=5, cluster_radius=2.5, distance_to_soma=1):
        sections = self.sections_basal + self.sections_apical
        all_sections = self.all_sections

        self.distance_to_soma = distance_to_soma
        dist_list = self.class_dict.get(distance_to_soma, [])
        # sections directly connected to soma
        sections = [section for i, section in enumerate(all_sections) if i in dist_list]

        e_syn, tau1, tau2, syn_weight = self.syn_param_exc

        self.num_syn_clustered = num_syn_clustered
        self.num_clusters = num_clusters
        self.cluster_radius = cluster_radius
        num_syn = num_syn_clustered - num_clusters

        # points_per_cluster = np.ceil(np.random.normal(num_syn_clustered/num_clusters - 1, 2, num_clusters))
        # points_per_clutser = [max(int(round(i)),0) for i in points_per_cluster]

        section_cluster_list = []

        for i in tqdm(range(num_clusters)):

            # could add a new attribute, order, for each section;
            # and then extract different number of sections from different orders
            # Section = self.rnd.choice(self.sections_basal+self.sections_apical)

            # Only select the sections directly connected to soma
            # section = self.rnd.choice(sections)[0].sec
            section = self.rnd.choice(sections)[0].sec

            section_cluster_list.append(section)

            section_name = section.psection()['name']
            section_id_synapse = self.section_df.loc[self.section_df['section_name'] == section_name, 'section_id'].values[0]

            # loc = self.rnd.uniform()
            loc = 0.5 # for center

            segment_synapse = section(loc)

            synapse = h.Exp2Syn(segment_synapse)
            synapse.e = e_syn
            synapse.tau1 = tau1
            synapse.tau2 = tau2

            data_to_append = {'section_id_synapse': section_id_synapse,
                                        'section_synapse': section,
                                        'segment_synapse': segment_synapse,
                                        'synapse': synapse,
                                        'netstim': None,
                                        'random': None,
                                        'netcon': None,
                                        'loc': loc,
                                        'type': 'C',
                                        'cluster_id': i}

            self.section_synapse_df = self.section_synapse_df.append(data_to_append, ignore_index=True)
            # self.section_synapse_df = pd.concat([self.section_synapse_df, pd.DataFrame(data_to_append, index=[0])], ignore_index=True)

        for _ in tqdm(range(num_syn)):
            # available_index_section_cluster_list = [i for i, count in enumerate(points_per_cluster) if count > 0]
            # available_section_cluster_list = [section_cluster_list[i] for i in available_index_section_cluster_list]
            # section_cluster = self.rnd.choice(available_section_cluster_list)
            section_cluster = self.rnd.choice(section_cluster_list)
            section_cluster_index = section_cluster_list.index(section_cluster)
            section_name_cluster = section_cluster.psection()['name']
            section_id_synapse_cluster = self.section_df.loc[self.section_df['section_name'] == section_name_cluster, 'section_id'].values[0]

            # use exponential distribution to generate loc
            dis_from_center = self.rnd.exponential(1/cluster_radius)

            if self.rnd.random() < 0.5:
                loc = 0.5 + dis_from_center / section_cluster.L
            else:
                loc = 0.5 - dis_from_center / section_cluster.L

            start_point = 0.5

            while loc > 1 or loc < 0:
                if loc > 1:
                    if list(self.DiG.successors(section_id_synapse_cluster)) != []:
                        dis_from_center = dis_from_center - section_cluster.L * (1 - start_point)
                        section_id_synapse_cluster = self.rnd.choice(list(self.DiG.successors(section_id_synapse_cluster)))
                        section_cluster = all_sections[section_id_synapse_cluster][0].sec
                        loc = dis_from_center / section_cluster.L
                        start_point = 0
                    else:
                        loc = 1
                elif loc < 0:
                    if list(self.DiG.predecessors(section_id_synapse_cluster)) != []:
                        dis_from_center = dis_from_center - section_cluster.L * (start_point - 0)
                        section_id_synapse_cluster = self.rnd.choice(list(self.DiG.predecessors(section_id_synapse_cluster)))
                        section_cluster = all_sections[section_id_synapse_cluster][0].sec
                        loc = 1 - dis_from_center / section_cluster.L
                        start_point = 1
                    else:
                        loc = 0

            segment_synapse = section_cluster(loc)

            synapse = h.Exp2Syn(segment_synapse)
            synapse.e = e_syn
            synapse.tau1 = tau1
            synapse.tau2 = tau2

            data_to_append = {'section_id_synapse': section_id_synapse_cluster,
                                        'section_synapse': section_cluster,
                                        'segment_synapse': segment_synapse,
                                        'synapse': synapse,
                                        'netstim': None,
                                        'random': None,
                                        'netcon': None,
                                        'loc': loc,
                                        'type': 'C',
                                        'cluster_id': section_cluster_index}

            self.section_synapse_df = self.section_synapse_df.append(data_to_append, ignore_index=True)
            # self.section_synapse_df = pd.concat([self.section_synapse_df, pd.DataFrame(data_to_append, index=[0])], ignore_index=True)

            time.sleep(0.01)

        self.type_array = self.section_synapse_df['type'].values

        # self.add_clustered_inputs(syn_weight, num_clusters)

    def set_synapse_type(self):
        rnd = self.rnd
        type_list = ['A', 'B']
        num_clusters_per_type = (5, 11)
        min_points = 10
        max_points = 100
        center_type_prob = 1

        type_array = np.array([''] * self.num_syn, dtype=str)

        num_types = len(type_list)
        cluster_centers = []

        section_id_synapse_list = self.section_id_synapse_list
        distance_matrix = self.distance_matrix

        for i in range(num_types):
            num_clusters = np.random.randint(*num_clusters_per_type)
            cluster_centers_type = np.random.choice(section_id_synapse_list, num_clusters, replace=False)
            cluster_centers.extend([(center, type_list[i]) for center in cluster_centers_type])

        for centers, ptype in cluster_centers:
            idx = np.where(section_id_synapse_list == centers)[0]
            type_array[idx] = ptype

        while '' in type_array:
            for centers, ptype in cluster_centers:
                num_points = np.random.randint(min_points, max_points + 1)
                if np.count_nonzero(type_array == '') < self.num_syn / 2:
                    num_points = np.random.randint(min_points, min(max_points, 31))
                distances = distance_matrix[:, centers]
                eligible_indices = np.where(type_array == '')[0]
                nearest_indices = eligible_indices[np.argsort(distances[eligible_indices])[:num_points]]

                # type_array[nearest_indices] = ptype
                non_center_types = [t for t in type_list if t != ptype]
                chosen_type = rnd.choice([ptype] + non_center_types, p=[center_type_prob] + [(1 - center_type_prob) / (num_types - 1)] * (num_types - 1), size=len(nearest_indices))
                type_array[nearest_indices] = chosen_type

        self.type_array = type_array

    #cannot use jit for this function
    # @jit
    def calculate_distance_matrix(self, distance_limit=2000):
        loc_array, section_id_synapse_list = self.loc_array, self.section_id_synapse_list
        parentID_list, length_list = self.section_df['parent_id'].values, self.section_df['length'].values

        length_list = np.array(length_list)
        distance_matrix = np.zeros((self.num_syn, self.num_syn))
        for i in range(self.num_syn):
            for j in range(i + 1, self.num_syn):
                    m = section_id_synapse_list[i]
                    n = section_id_synapse_list[j]

                    path = self.sp[m][n]

                    if len(path) > 1:
                        loc_i = loc_array[i] * (parentID_list[m] == path[1]) + (1-loc_array[i]) * (parentID_list[m] != path[1])
                        loc_j = loc_array[j] * (parentID_list[n] == path[-2]) + (1-loc_array[j]) * (parentID_list[n] != path[-2])

                        mask_i = np.array(path) == m
                        mask_j = np.array(path) == n

                        distance = np.sum(length_list[path] * (mask_i * loc_i + mask_j * loc_j))
                        # for k in path:
                        #     if k == m:
                        #         distance = length_list[k]*loc_i
                        #     if k == n:
                        #         distance = length_list[k]*loc_j
                        #     distance = distance + length_list[k]
                    else:
                        distance = length_list[m] * abs(loc_array[i] - loc_array[j])

                    distance_matrix[i, j] = distance_matrix[j, i] = distance

        self.distance_matrix = distance_matrix

        return distance_matrix

    def visualize_distance(self):
        type_array, distance_matrix = self.section_synapse_df['type'].values, self.distance_matrix

        distance_list, distance_a_a_list, distance_b_b_list = [], [], []
        for i in range(self.num_syn):
            for j in range(self.num_syn):
                if i < j:
                    if self.initialize_cluster_flag == False:
                        distance_list.append(distance_matrix[i,j])
                        if type_array[i] == type_array[j] == 'A' :#and distance_matrix[i,j] < distance_limit:
                            distance_a_a_list.append(distance_matrix[i,j])
                        if type_array[i] == type_array[j] == 'B' :#and distance_matrix[i,j] < distance_limit:
                            distance_b_b_list.append(distance_matrix[i,j])
                    else:
                        distance_list.append(distance_matrix[i,j])

        plt.figure(figsize=(5, 5))
        if self.initialize_cluster_flag == False:
            sns.histplot(distance_a_a_list, kde=True,stat="density",color='lightskyblue',linewidth=0,label='A-A')
            sns.histplot(distance_b_b_list,kde=True,stat="density",color='orange',linewidth=0,label='B-B')
        sns.histplot(distance_list,kde=True,stat="density",color='lightgreen',linewidth=0,label='All')
        plt.legend()
        plt.xlabel('Distance (microns)')
        plt.ylabel('Probability')
        # plt.xlim(0,distance_limit)
        # plt.ylim(0,0.004)
        plt.title('Distance distribution before clustered')

    def set_type_array(self, new_type_array):
        self.type_array = new_type_array

    def get_cell(self):
        return self.complex_cell


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Fri Nov 26 09:06:38 2021

@author: Surgery Adhi Nugroho
NIM: 065002100015
"""
def fungsidata (biodata):
    file = open ('Biodata.txt','w')
    file.write(biodata)
    file.close()

def write ():
    file = open ('Biodata.txt', 'r')
    text = file.read()
    print(text)
    file.close

nama = input ('Nama: ')
usia = input ('Usia: ')
alamat = input ('Alamat: ')
email = input ('Email: ')
pengajar = input ('Dosen Walimu: ')
biodata = 'Nama: {}\nUsia: {}\nAlamat: {}\nEmail: {}\nPengajar: {}'.format(nama,usia,alamat,email,pengajar)
print('')
print('Biodata Anda')
print('===================')
fungsidata(biodata)
write()


# 50670324

def sleight_of_hand(k, nums):
    """Перебираем числа от 1 до 9 и проверяем
    получится ли нажать на все клавиши с этими числами. """
    result = 0
    for t in range(10):
        count = nums.count(str(t))
        if 0 < count <= int(k) * 2:
            result += 1
    return result

def main():
    with open('input.txt') as f:
        k = f.readline()
        nums = f.read()
        print(sleight_of_hand(k, nums))

if __name__ == '__main__':
    main()


from threading import Thread
import time

# 含有参数的线程函数
def fun(sec,name):
    print('%s线程开始执行'%name)
    time.sleep(sec)
    print('%s线程执行完毕'%name)

# 创建多个线程
jobs = []
for i in range(5):
    t = Thread(target=fun,args=(2,),kwargs={'name':'%d线程'%i})
    jobs.append(t)
    t.start()
for i in jobs:
    i.join()


'''
Given a list of accounts where each element accounts[i] is a list of strings, where the first element accounts[i][0] is a name, and the rest of the elements are emails representing emails of the account.

Now, we would like to merge these accounts. Two accounts definitely belong to the same person if there is some common email to both accounts. Note that even if two accounts have the same name, they may belong to different people as people could have the same name. A person can have any number of accounts initially, but all of their accounts definitely have the same name.

After merging the accounts, return the accounts in the following format: the first element of each account is the name, and the rest of the elements are emails in sorted order. The accounts themselves can be returned in any order.

Example 1:

Input: accounts = [["John","johnsmith@mail.com","john_newyork@mail.com"],["John","johnsmith@mail.com","john00@mail.com"],["Mary","mary@mail.com"],["John","johnnybravo@mail.com"]]
Output: [["John","john00@mail.com","john_newyork@mail.com","johnsmith@mail.com"],["Mary","mary@mail.com"],["John","johnnybravo@mail.com"]]
Explanation:
The first and second John's are the same person as they have the common email "johnsmith@mail.com".
The third John and Mary are different people as none of their email addresses are used by other accounts.
We could return these lists in any order, for example the answer [['Mary', 'mary@mail.com'], ['John', 'johnnybravo@mail.com'],
['John', 'john00@mail.com', 'john_newyork@mail.com', 'johnsmith@mail.com']] would still be accepted.
Example 2:

Input: accounts = [["Gabe","Gabe0@m.co","Gabe3@m.co","Gabe1@m.co"],["Kevin","Kevin3@m.co","Kevin5@m.co","Kevin0@m.co"],["Ethan","Ethan5@m.co","Ethan4@m.co","Ethan0@m.co"],["Hanzo","Hanzo3@m.co","Hanzo1@m.co","Hanzo0@m.co"],["Fern","Fern5@m.co","Fern1@m.co","Fern0@m.co"]]
Output: [["Ethan","Ethan0@m.co","Ethan4@m.co","Ethan5@m.co"],["Gabe","Gabe0@m.co","Gabe1@m.co","Gabe3@m.co"],["Hanzo","Hanzo0@m.co","Hanzo1@m.co","Hanzo3@m.co"],["Kevin","Kevin0@m.co","Kevin3@m.co","Kevin5@m.co"],["Fern","Fern0@m.co","Fern1@m.co","Fern5@m.co"]]

Constraints:

1 <= accounts.length <= 1000
2 <= accounts[i].length <= 10
1 <= accounts[i][j] <= 30
accounts[i][0] consists of English letters.
accounts[i][j] (for j > 0) is a valid email.
'''
from typing import *

import unittest

class UF:
    def __init__(self, n):
        self.roots = [i for i in range(n)]

    def union(self,u, v):
        root_u = self.find(u)
        root_v = self.find(v)
        if root_u == root_v:
            return
        self.roots[root_u] = root_v

    def find(self,u):
        while u != self.roots[u]:
            self.roots[u] = self.roots[self.roots[u]]
            u = self.roots[u]
        return u

class Solution:
    def accountsMerge(self, accounts: List[List[str]]) -> List[List[str]]:
        all_emails = set()
        for account in accounts:
            for u in account[1:]:
                all_emails.add((u, account[0]))

        all_emails = sorted(list(all_emails))
        maps = {k[0]: i for i, k in enumerate(all_emails)}

        uf = UF(len(all_emails))
        for account in accounts:
            u = maps[account[1]]
            for v in account[2:]:
                v = maps[v]
                uf.union(u, v)
        res = {}
        for u, _ in all_emails:
            uf.find(maps[u])
        for i in range(len(all_emails)):
            res.setdefault(uf.find(i), []).append(all_emails[i][0])
        ans = []
        for k, v in res.items():
            item = []
            item.append(all_emails[k][1])
            item.extend(v)
            ans.append(item)

        return ans

class TestSolution(unittest.TestCase):

    def test_case(self):
        examples = (
            (([["John","johnsmith@mail.com","john_newyork@mail.com"],["John","johnsmith@mail.com","john00@mail.com"],["Mary","mary@mail.com"],["John","johnnybravo@mail.com"]],), [["John","john00@mail.com","john_newyork@mail.com","johnsmith@mail.com"],["John","johnnybravo@mail.com"],["Mary","mary@mail.com"]]),
        )
        for first, second in examples:
            self.assert_function(first, second)

    def assert_function(self, first, second):
        self.assertEqual(Solution().accountsMerge(*first), second,
                         msg="first: {}; second: {}".format(first, second))

unittest.main()


#!/usr/bin/python
#-*-coding:utf-8-*-
'''@Date:18-10-30'''
'''@Time:下午3:33'''
'''@author: Duncan'''

import pandas as pd
import numpy as np
import Features as _F
import Models as _M
import PreProcess as _P
import feather
from sklearn.metrics import roc_auc_score
import os

pri_id = "UID"

data_path = '/data/ymzhou/SweetOrange/'
train_path = data_path + "train/"
test_path = data_path + "test/"
_train = pd.DataFrame()
_test = pd.DataFrame()

# trans_train_info = pd.read_csv(train_path + "transaction_TRAIN.csv")
trans_train_info = pd.read_csv(train_path + "transaction_train_new.csv")
# trans_test_info = pd.read_csv(test_path + "transaction_round1.csv")
trans_test_info = pd.read_csv(test_path + "test_transaction_round2.csv")

# op_train_info = pd.read_csv(train_path + "operation_TRAIN.csv")
op_train_info = pd.read_csv(train_path + "operation_train_new.csv")
# op_test_info = pd.read_csv(test_path + "operation_round1.csv")
op_test_info = pd.read_csv(test_path + "test_operation_round2.csv")

_train[pri_id] = pd.concat((op_train_info[pri_id],trans_train_info[pri_id])).unique()
_test[pri_id] = pd.concat((op_test_info[pri_id],trans_test_info[pri_id])).unique()

# label = pd.read_csv(train_path + "tag_TRAIN.csv")
label = pd.read_csv(train_path + "tag_train_new.csv")

op_info = pd.concat((op_train_info,op_test_info))

trans_info = pd.concat((trans_train_info,trans_test_info))

# 处理操作数据
def ProcessOperation(df):

    data = pd.DataFrame()
    data[pri_id] = pd.concat((_train[pri_id],_test[pri_id]))
    # day处理 (分为上 中 下旬)
    temp = _F.MonthCount(df,pri_id)
    data = pd.merge(data,temp,on=pri_id,how='left')

    # mode
    temp = _F.CatRowsToCols(df,pri_id,'mode','os')
    data = pd.merge(data,temp,on=pri_id,how='left')

    # success
    temp = _F.CatRowsToCols(df,pri_id,'success','os')
    data = pd.merge(data,temp,on=pri_id,how='left')

    # version
    temp = _F.CatRowsToCols(df,pri_id,'version','os')
    data = pd.merge(data,temp,on=pri_id,how='left')

    # time
    df['day_period'] = df['time'].apply(_F.TimeInterval)
    temp = _F.CatRowsToCols(df,pri_id,'day_period','os')
    data = pd.merge(data,temp,on=pri_id,how='left')

    cols = ['device2','ip1','ip2','mac1','mac2','device_code1','device_code2','device_code3']

    for col in cols:
        # device2 (用户有多少不同型号的设备)
        temp = _F.GetCount(df,pri_id,col,'os')
        data = pd.merge(data,temp,on=pri_id,how='left')

    # 统计地理位置次数
    temp = _F.CountWS(df)
    data = pd.merge(data,temp,on=pri_id,how='left')
    #
    # # 危险设备（安卓）
    # temp = _F.CountDangerous(df,label,pri_id,'Tag','device_code1','time',500)
    # data = pd.merge(data,temp,on=pri_id,how='left')

    # 危险设备（安卓）
    # temp = _F.CountDangerous(df,label,pri_id,'Tag','device_code2','time',500)
    # data = pd.merge(data,temp,on=pri_id,how='left')

    #
    # # 危险设备（苹果）
    # temp = _F.CountDangerous(df,label,pri_id,'Tag','device_code3','time',200)
    # data = pd.merge(data,temp,on=pri_id,how='left')

    # 统计危险地理位置
    # temp = _F.CountDangerous(df,label,pri_id,'Tag','geo_code','time',500)
    # data = pd.merge(data,temp,on=pri_id,how='left')

    # 统计危险操作类型
    # temp = _F.CountDangerous(df,label,pri_id,'Tag','mode','time',10000)
    # data = pd.merge(data,temp,on=pri_id,how='left')

    # 统计危险mac1地址
    # temp = _F.CountDangerous(df,label,pri_id,'Tag','mac1','time',600)
    # data = pd.merge(data,temp,on=pri_id,how='left')

    # 统计危险mac2地址
    # temp = _F.CountDangerous(df,label,pri_id,'Tag','mac2','time',1000)
    # data = pd.merge(data,temp,on=pri_id,how='left')

    # 统计危险ip1地址
    # temp = _F.CountDangerous(df,label,pri_id,'Tag','ip1','time',1000)
    # data = pd.merge(data,temp,on=pri_id,how='left')

    # 统计危险ip1_sub地址
    # temp = _F.CountDangerous(df,label,pri_id,'Tag','ip1_sub','time',1000)
    # data = pd.merge(data,temp,on=pri_id,how='left')

    # 统计危险ip2_sub地址
    # temp = _F.CountDangerous(df,label,pri_id,'Tag','ip2_sub','time',50)
    # data = pd.merge(data,temp,on=pri_id,how='left')

    # 统计每个用户最常出现的经纬度
    # temp = _F.PositionWS(df)
    # data = pd.merge(data,temp,on=pri_id,how='left')

    # 地理位置聚类

    data = data.fillna(0)
    return data

# 处理交易数据
def ProcessTrans(df):
    data = pd.DataFrame()
    data[pri_id] = pd.concat((_train[pri_id],_test[pri_id]))

    # channel 统计次数
    temp = _F.CatRowsToCols(df,pri_id,'channel','day')
    data = pd.merge(data,temp,on=pri_id,how='left')

    # day
    temp = _F.MonthCount(df,pri_id)
    data = pd.merge(data,temp,on=pri_id,how='left')

    # time
    df['day_period'] = df['time'].apply(_F.TimeInterval)
    temp = _F.CatRowsToCols(df,pri_id,'day_period','day')
    data = pd.merge(data,temp,on=pri_id,how='left')

    # trans_amt (交易金额)(最大值，最小值，平均值)
    # temp = _F.GetValMaxMin(df,pri_id,'trans_amt')
    # data = pd.merge(data,temp,on=pri_id,how='left')
    # temp = _F.GetValAvg(df,pri_id,'trans_amt')
    # data = pd.merge(data,temp,on=pri_id,how='left')
    # temp = _F.GetValSum(df,pri_id,'trans_amt')
    # data = pd.merge(data,temp,on=pri_id,how='left')

    # 计算不同的次数
    cols = ['device2','ip1','mac1','device_code1','device_code2','device_code3','amt_src1','amt_src2','merchant','trans_type1','trans_type2','acc_id1','market_type','market_code']
    for col in cols:
        temp = _F.GetCount(df,pri_id,col,'day')
        data = pd.merge(data,temp,on=pri_id,how='left')

    # 脱敏后的余额(最大值，最小值，平均值)
    # temp = _F.GetValMaxMin(df,pri_id,'bal')
    # data = pd.merge(data,temp,on=pri_id,how='left')
    # temp = _F.GetValAvg(df,pri_id,'bal')
    # data = pd.merge(data,temp,on=pri_id,how='left')
    # temp = _F.GetValSum(df,pri_id,'bal')
    # data = pd.merge(data,temp,on=pri_id,how='left')

    # 危险设备（安卓）
    # temp = _F.CountDangerous(df,label,pri_id,'Tag','device_code1','time',100)
    # data = pd.merge(data,temp,on=pri_id,how='left')
    #
    # # 危险设备（安卓）
    # temp = _F.CountDangerous(df,label,pri_id,'Tag','device_code2','time',100)
    # data = pd.merge(data,temp,on=pri_id,how='left')

    # 危险设备（苹果）
    # temp = _F.CountDangerous(df,label,pri_id,'Tag','device_code3','time',80)
    # data = pd.merge(data,temp,on=pri_id,how='left')

    # 统计危险地理位置
    # temp = _F.CountDangerous(df,label,pri_id,'Tag','geo_code','time',200)
    # data = pd.merge(data,temp,on=pri_id,how='left')

    # 统计危险商家
    # temp = _F.CountDangerous(df,label,pri_id,'Tag','merchant','time',1000)
    # data = pd.merge(data,temp,on=pri_id,how='left')
    #
    # # 统计地理位置次数
    temp = _F.CountWS(df)
    data = pd.merge(data,temp,on=pri_id,how='left')
    #
    # 统计危险mac1地址
    # temp = _F.CountDangerous(df,label,pri_id,'Tag','mac1','time',600)
    # data = pd.merge(data,temp,on=pri_id,how='left')

    # 统计危险ip1地址
    # temp = _F.CountDangerous(df,label,pri_id,'Tag','ip1','time',1000)
    # data = pd.merge(data,temp,on=pri_id,how='left')

    # 统计危险ip1_sub地址
    # temp = _F.CountDangerous(df,label,pri_id,'Tag','ip1_sub','time',1000)
    # data = pd.merge(data,temp,on=pri_id,how='left')

    # 统计危险交易账户
    # temp = _F.CountDangerous(df,label,pri_id,'Tag','acc_id1','time',200)
    # data = pd.merge(data,temp,on=pri_id,how='left')
    #
    # # 统计危险转出账户
    # temp = _F.CountDangerous(df,label,pri_id,'Tag','acc_id2','time',100)
    # data = pd.merge(data,temp,on=pri_id,how='left')
    #
    # # 统计危险转入账户
    # temp = _F.CountDangerous(df,label,pri_id,'Tag','acc_id3','time',100)
    # data = pd.merge(data,temp,on=pri_id,how='left')

    # 统计每个用户最常出现的经纬度
    # temp = _F.PositionWS(df)
    # data = pd.merge(data,temp,on=pri_id,how='left')

    data = data.fillna(0)
    return data

# 处理操作和交易数据，处理列名
def Base_Process(encode_type='LabelEncode'):
    if os.path.exists(data_path + "data/data.feather"):
        df = feather.read_dataframe(data_path + "data/data.feather")
        return df

    data = pd.DataFrame()
    data[pri_id] = pd.concat((_train[pri_id],_test[pri_id]))

    # 这几列的值要按pri_id相加

    cols = ['device2','ip1','mac1','device_code1','device_code2','device_code3']
    cols = [col+"_count" for col in cols]
    op = ProcessOperation(op_info)
    trans = ProcessTrans(trans_info)

    temp = pd.DataFrame()
    temp[cols] = op[cols] + trans[cols]

    c_cols = ['device_code1','device_code2','device_code3','geo_code','mac1','ip1','ip1_sub']
    c_cols = [col+"_d_label" for col in c_cols]

    temp_c = pd.DataFrame()
    temp_c[c_cols] = op[c_cols] + trans[c_cols]

    # 对op和trans列名重新编码,防止重复名称
    _Prep = _P.Process()

    cols.append(pri_id)
    op = _Prep.RenameColumns(op,cols,'op')
    trans = _Prep.RenameColumns(trans,cols,'trans')

    cols.remove(pri_id)

    op_cols = [col for col in op.columns if col not in cols and col not in c_cols]
    tran_cols = [col for col in trans.columns if col not in cols and col not in c_cols]

    # op_cols = [col for col in op.columns if col not in cols]
    # tran_cols = [col for col in trans.columns if col not in cols]

    data = pd.merge(data,op[op_cols],on=pri_id,how='left')

    data = pd.merge(data,trans[tran_cols],on=pri_id,how='left')

    # 连接temp
    data = pd.concat((data,temp),axis=1)
    data = pd.concat((data,temp_c),axis=1)

    # 加入label
    data = pd.merge(data,label,on=pri_id,how='left')
    data.rename(columns={'Tag':'y'},inplace=True)

    # 编码
    _Prep = _P.Process()
    data =_Prep.CatColConvert(data,pri_id,encode_type)

    data = data.fillna(0)

    # 持久化
    feather.write_dataframe(data,data_path + "data/data.feather")
    return data

# 聚类地址
def _F_Clsuter_Geo():
    if os.path.exists(data_path + "data/_F_geo.feather"):
        df = feather.read_dataframe(data_path + "data/_F_geo.feather")
        return df
    # 合并 operation和transaction的uid，geo_code
    geo_info = pd.concat((op_info[[pri_id,'geo_code']],trans_info[[pri_id,'geo_code']]))
    geo_info['pos'] = geo_info['geo_code'].apply(_F.Decode)

    temp = geo_info[geo_info['pos'] != -1]

    res = [x for x in temp['pos'].values]
    X = np.asarray(res)
    from sklearn.cluster import KMeans
    kmeans = KMeans(n_clusters=20, random_state=2018).fit(X)
    temp['cluster_id'] = kmeans.labels_

    t = temp.groupby(['UID','cluster_id'])['pos'].count().reset_index().rename(columns={'pos':'cluster_count'})
    c = pd.pivot_table(t,index='UID',columns='cluster_id',values='cluster_count').fillna(0).reset_index()
    # 重命名列
    _Prep = _P.Process()
    c = _Prep.RenameColumns(c,[pri_id],'cluster')
    # 持久化
    feather.write_dataframe(c,data_path + "data/_F_geo.feather")
    return c

# 加入用户的省份 城市 区
def _F_GeoCode(encode_type="LabelEncode",n=3):
    if os.path.exists(data_path + "data/_F_geo_code.feather"):
        df = feather.read_dataframe(data_path + "data/_F_geo_code.feather")
        return df
    # 取每个用户经常活跃的topN geo_code
    geo_info = pd.concat((op_info[[pri_id,'geo_code','day']],trans_info[[pri_id,'geo_code','day']]))
    temp = _F.TopNGeo_code(geo_info,pri_id,'day',n)
    # 编码
    _Prep = _P.Process()
    temp =_Prep.CatColConvert(temp,pri_id,encode_type)
    # 持久化
    feather.write_dataframe(temp,data_path + "data/_F_geo_code.feather")
    return temp

# 加入TopN的nunique
def _F_nunique(n=3):
    if os.path.exists(data_path + "data/_F_nunique.feather"):
        df = feather.read_dataframe(data_path + "data/_F_nunique.feather")
        return df

    temp = pd.DataFrame()
    temp[pri_id] = pd.concat((_train[pri_id],_test[pri_id]))

    # 增加天和月份的片
    df = pd.concat((op_info[[pri_id,'day']],trans_info[[pri_id,'day']]))
    month = _F.Day2Month(df,'day')
    temp = pd.merge(temp,_F.TopN_col_distinct_count(month[[pri_id,'month','day']],pri_id,'month','day',n),on=pri_id,how='left')

    df = pd.concat((op_info[[pri_id,'time','day']],trans_info[[pri_id,'time','day']]))
    df['day_period'] = df['time'].apply(_F.TimeInterval)
    temp = pd.merge(temp,_F.TopN_col_distinct_count(df[[pri_id,'day_period','day']],pri_id,'day_period','day',n))

    for col in ['geo_code','ip1','ip1_sub','mac1','merchant','ip2','ip2_sub','mode','mac2','os','channel','trans_type1','trans_type2','code1','code2','market_code','market_type','device_code1','device_code2','device_code3','wifi']:
        if col in ['merchant','channel','trans_type1','trans_type2','code1','code2','market_code','market_type']:
            df = trans_info[[pri_id,col,'day']]
        elif col in ['ip2','ip2_sub','mode','mac2','os','wifi']:
            df = op_info[[pri_id,col,'day']]
        else:
            df = pd.concat((op_info[[pri_id,col,'day']],trans_info[[pri_id,col,'day']]))
        temp = pd.merge(temp,_F.TopN_col_distinct_count(df,pri_id,col,'day',n),on=pri_id,how='left')

    feather.write_dataframe(temp,data_path + "data/_F_nunique.feather")
    return temp

def _F_nunique_ratio(n=3):

    if os.path.exists(data_path + "data/_F_nunique_ratio.feather"):
        df = feather.read_dataframe(data_path + "data/_F_nunique_ratio.feather")
        return df

    temp = pd.DataFrame()
    temp[pri_id] = pd.concat((_train[pri_id],_test[pri_id]))

    # 增加天和月份的片
    df = pd.concat((op_info[[pri_id,'day']],trans_info[[pri_id,'day']]))
    month = _F.Day2Month(df,'day')
    temp = pd.merge(temp,_F.TopN_col_distinct_ratio(month[[pri_id,'month','day']],pri_id,'month','day',n),on=pri_id,how='left')

    df = pd.concat((op_info[[pri_id,'time','day']],trans_info[[pri_id,'time','day']]))
    df['day_period'] = df['time'].apply(_F.TimeInterval)
    temp = pd.merge(temp,_F.TopN_col_distinct_ratio(df[[pri_id,'day_period','day']],pri_id,'day_period','day',n))

    # topN ratio
    for col in ['geo_code','ip1','ip1_sub','mac1','merchant','ip2','ip2_sub','mode','mac2','os','channel','trans_type1','trans_type2','code1','code2','market_code','market_type','device_code1','device_code2','device_code3','wifi']:
        if col in ['merchant','channel','trans_type1','trans_type2','code1','code2','market_code','market_type']:
            df = trans_info[[pri_id,col,'day']]
        elif col in ['ip2','ip2_sub','mode','mac2','os','wifi']:
            df = op_info[[pri_id,col,'day']]
        else:
            df = pd.concat((op_info[[pri_id,col,'day']],trans_info[[pri_id,col,'day']]))
        temp = pd.merge(temp,_F.TopN_col_distinct_ratio(df,pri_id,col,'day',n),on=pri_id,how='left')

    feather.write_dataframe(temp,data_path + "data/_F_nunique_ratio.feather")
    return temp

# market_code
def _F_market():

    if os.path.exists(data_path + "data/_F_market_ratio.feather"):
        df = feather.read_dataframe(data_path + "data/_F_market_ratio.feather")
        return df

    # 计算每个用户消费次数以及营销活动占比
    temp = trans_info.groupby('UID')['day'].count().reset_index().rename(columns={'day':'trans_count'})
    temp = temp.merge(trans_info.groupby('UID')['market_code'].agg({'market_count':'count'}),on='UID',how='left')
    temp['market_ratio'] = temp['market_count'] / temp['trans_count']

    feather.write_dataframe(temp,data_path + "data/_F_market_ratio.feather")
    return temp

# 加入危险label占比
def _F_LabelCount():
    if os.path.exists(data_path + "data/_F_d_label_count.feather"):
        df = feather.read_dataframe(data_path + "data/_F_d_label_count.feather")
        return df

    temp = pd.DataFrame()
    temp[pri_id] = pd.concat((_train[pri_id],_test[pri_id]))

    # trans_type1
    temp = temp.merge(_F.DangerousLabel(trans_info,pri_id,'trans_type1','ced62357ad496957',0.8),on=pri_id,how='left')

    # trans_type2
    temp = temp.merge(_F.DangerousLabel(trans_info,pri_id,'trans_type2',104,0.8),on=pri_id,how='left')

    # amt_src1
    temp = temp.merge(_F.DangerousLabel(trans_info,pri_id,'amt_src1','c4ec9622cf5c6e55',0.8),on=pri_id,how='left')

    # amt_src2
    temp = temp.merge(_F.DangerousLabel(trans_info,pri_id,'amt_src2','c4ec9622cf5c6e55',0.8),on=pri_id,how='left')
    feather.write_dataframe(temp,data_path + "data/_F_d_label_count.feather")

    return temp

# 验证结果
def Metric(model='lgb',encode_type="LabelEncode"):
    df = Base_Process(encode_type)
    # 加一个地理位置聚类
    df = pd.merge(df,_F_Clsuter_Geo(),on=pri_id,how='left')

    # 加一个用户活跃TopN省份 市 区
    temp = _F_GeoCode(n=1)
    df = pd.merge(df,temp,on=pri_id,how='left')

    # 加入distinct的统计
    temp = _F_nunique(3)
    df = pd.merge(df,temp,on=pri_id,how='left')

    # 加入ratio的统计
    temp = _F_nunique_ratio(3)
    df = pd.merge(df,temp,on=pri_id,how='left')

    # 加入market count 和 参与活动率
    temp = _F_market()
    df = pd.merge(df,temp,on=pri_id,how='left')

    # 填充缺失值
    df = df.fillna(0)

    # 构造训练集
    df = pd.merge(_train,df,on=pri_id,how='left')

    cols = [col for col in df.columns if col != 'y']
    Train = df[cols]
    Label = df['y']

    # 加入nn的特征
    # _mlp_features = pd.read_csv(data_path + "data/_F_mlp_features.csv")
    # print(_mlp_features.shape)
    # print(Train.shape)
    # Train = pd.merge(Train,_mlp_features,on=pri_id,how='left')
    data = _M.N_Split(Train,Label,n=5)
    auc = 0
    score = 0
    for _d in data:
        _train_x = _d[0]
        _train_y = _d[1]
        _test_x = _d[2]
        _test_y = _d[3]
        features = [col for col in _train_x.columns if col != pri_id and col != 'y']
        # 计算auc和本题评分标准
        pred = _M.Predict(_train_x,features,_train_y,_test_x,model)
        _t_auc = roc_auc_score(_test_y,pred)
        auc += _t_auc
        print("auc is %.4f" % _t_auc)
        # 本题评价标准
        _t_score = _M.tpr_weight_funtion(_test_y,pred)
        score += _t_score
        print("score is %.4f" % _t_score)
    auc /= 5
    score /= 5
    print("avg auc is %.4f" % auc)
    print("avg score is %.4f" % score)

# 添加神经网络预测方法
def NeuralNetwork(encode_type="LabelEncode"):
    df = Base_Process(encode_type)
    # 加一个地理位置聚类
    df = pd.merge(df,_F_Clsuter_Geo(),on=pri_id,how='left')

    # 加一个用户活跃TopN省份 市 区
    temp = _F_GeoCode(n=1)
    df = pd.merge(df,temp,on=pri_id,how='left')

    # 加入distinct的统计
    temp = _F_nunique(3)
    df = pd.merge(df,temp,on=pri_id,how='left')

    # 加入ratio的统计
    temp = _F_nunique_ratio(3)
    df = pd.merge(df,temp,on=pri_id,how='left')

    _Train = pd.merge(_train,df,on=pri_id,how='left').fillna(0)
    _Test = pd.merge(_test,df,on=pri_id,how='left').fillna(0)
    features = [col for col in _Train.columns if col != pri_id and col != 'y']

    _Label = _Train['y']

    # 数据输入和结构构造
    from keras.models import Sequential
    model = Sequential()
    from keras.layers import Dense, Activation, Conv2D, MaxPooling2D, Flatten, Dropout
    from keras import backend as K
    import tensorflow as tf
    import itertools

    shape = _Train.shape
    # 卷积层
    # model.add(Conv2D(64, (3,3), activation='relu', input_shape = (shape[0],shape[1],1)))
    # # 池化层
    # model.add(MaxPooling2D(pool_size=(2,2)))
    # # 全连接层 (设置输出层的维度)
    # model.add(Dense(256, activation='relu'))
    # # dropout层
    # model.add(Dropout(0.5))
    # # 最后全连接层，输出概率
    # model.add(Dense(1, activation='sigmoid'))

    # MLP
    # print(shape)
    model.add(Dense(64, input_dim=402, activation='relu'))
    model.add(Dropout(0.5))
    model.add(Dense(64, activation='relu'))
    model.add(Dropout(0.5))
    model.add(Dense(1, activation='sigmoid'))
    # 编译(后面要换成自己定义的评价函数)

    # 本题评分标准
    def tpr_weight_funtion(y_true,y_pred):

        # batch_size, n_elems = y_pred.shape[0],y_pred.shape[1]
        # idxs = list(itertools.permutations(range(n_elems)))
        # permutations = tf.gather(y_pred, idxs, axis=-1)  # Shape=(batch_size, n_permutations, n_elems)

        d = pd.DataFrame()
        # sess = tf.Session()
        # sess.run(tf.global_variables_initializer())
        # d['prob'] = permutations.eval(session=sess)

        d['prob'] = list(K.eval(y_pred))
        d['y'] = list(y_true)
        d = d.sort_values(['prob'], ascending=[0])
        y = d.y
        PosAll = pd.Series(y).value_counts()[1]
        NegAll = pd.Series(y).value_counts()[0]
        pCumsum = d['y'].cumsum()
        nCumsum = np.arange(len(y)) - pCumsum + 1
        pCumsumPer = pCumsum / PosAll
        nCumsumPer = nCumsum / NegAll
        TR1 = pCumsumPer[abs(nCumsumPer-0.001).idxmin()]
        TR2 = pCumsumPer[abs(nCumsumPer-0.005).idxmin()]
        TR3 = pCumsumPer[abs(nCumsumPer-0.01).idxmin()]
        return 0.4 * TR1 + 0.3 * TR2 + 0.3 * TR3

    def AUC(y_true,y_pred):
        not_y_pred = np.logical_not(y_pred)
        y_int1 = y_true*y_pred
        y_int0 = np.logical_not(y_true)*not_y_pred
        TP = np.sum(y_pred*y_int1)
        FP = np.sum(y_pred)-TP
        TN = np.sum(not_y_pred*y_int0)
        FN = np.sum(not_y_pred)-TN
        TPR = np.float(TP)/(TP+FN)
        FPR = np.float(FP)/(FP+TN)
        return ((1+TPR-FPR)/2)

    model.compile(optimizer='rmsprop',loss='binary_crossentropy',metrics=['accuracy'])
    # model.compile(optimizer='rmsprop',loss='binary_crossentropy',metrics=[AUC])

    # 训练 (batch_size 每次迭代选择的样本数)

    res = pd.DataFrame()
    res[pri_id] = _Test[pri_id]
    _K_Train = pd.DataFrame()
    _KTrain = pd.DataFrame()
    _KTrain[pri_id] = _Train[pri_id]
    # 需要将输入归一化
    _Train,_Test = _M.Normalize(_Train[features],_Test[features])

    from sklearn.model_selection import StratifiedKFold
    # 将_Train分成5份，5折之后求平均
    skf = StratifiedKFold(n_splits=5)
    pred = np.zeros((_Test.shape[0],1))

    for train,test in skf.split(_Train,_Label):

        model.fit(_Train.iloc[train],_Label.iloc[train],epochs=50,batch_size=128)
        # 连接剩下的一折和test
        temp = model.predict(_Test)
        pred += np.asarray(temp)
        _K_T = pd.DataFrame()
        _K_T[pri_id] = _KTrain.iloc[test][pri_id]
        _K_T['mlp'] = model.predict(_Train.iloc[test])
        _K_Train = pd.concat((_K_Train,_K_T))

    pred /= 5
    # 全连接的输出
    res['mlp'] = pred
    res = pd.concat((_K_Train,res))
    res.to_csv(data_path + "data/_F_mlp_features.csv",index=False)

# 用NN编码op和trans
def Encoder(target_col,encode_dim=5):
    # data_path = "./"
    # mode和trans_type1，merchant
    if target_col in ['mode']:
        data = op_info
    if target_col in ['trans_typd1','merchant']:
        data = trans_info
    keys,matrix = _F.simple_countVectorizer(data,data_path,pri_id,target_col)
    res = _F.AutoEncoder(matrix,encode_dim)
    temp = pd.DataFrame()
    temp[pri_id] = keys
    for i in range(encode_dim):
        temp[target_col + "_encode_" + str(i)] = res[:,i]
    temp.to_csv(data_path + "data/_F_encode_%s.csv" % target_col,index=False)
    return temp

# 序列编码
def SeqEncoder(df,pri_id,col,timesteps=5,encode_dim=5):
    mode = df[[pri_id,col,'day','time']]
    # 获取每个用户的操作序列，按时间
    # 按照天数降序排列，按照时间升序排列，然后按照UID聚集
    mode.sort_values(by=['day','time'],ascending=[False,True],inplace=True)
    temp = mode.groupby(pri_id)[col].apply(list).reset_index().rename(columns={col:'%s_seq' % col})
    temp['%s_seq_len' % col] = temp['%s_seq' % col].apply(lambda x : len(x))
    ob_len = int(temp['%s_seq_len' % col].max() * 3.0 / 5.0)
    # 改为timesteps的倍数
    while ob_len % timesteps != 0:
        ob_len += 1
    # 不足长度的序列进行填充(选取长度为最长长度的5/3)
    def Padding(x):

        if len(x) < ob_len:
            # 填充(Nan)
            x += ['Nan' for i in range(ob_len - len(x))]
        else:
            # 截取
            x = x[:ob_len]
        return x
    temp['%s_seq' % col] = temp['%s_seq' % col].apply(Padding)
    # 编码
    from sklearn.preprocessing import LabelEncoder
    le = LabelEncoder()
    mode['encoder'] = le.fit_transform(mode[col])
    # 建立字典
    seq_dic = {}
    encoder = mode[[col,'encoder']].drop_duplicates()
    for key,val in zip(encoder[col],encoder['encoder']):
        seq_dic[key] = val
    seq_dic['Nan'] = mode['encoder'].max() + 1
    # 对序列进行转化
    temp['%s_seq' % col] = temp['%s_seq' % col].apply(lambda x : [seq_dic[i] for i in x])
    matrix = np.asarray([i for i in temp['%s_seq' % col]])

    # 全连接层编码
    # res = _F.AutoEncoder(matrix,encode_dim)

    # LSTM编码
    res = _F.AutoEncoderLSTM(matrix,5,timesteps)
    # 保存结果
    results = pd.DataFrame()
    results[pri_id] = temp[pri_id]
    for i in range(encode_dim):
        results[col + "_encode_" + str(i)] = res[:,i]
    results.to_csv(data_path + "data/_F_seq_encode_%s.csv" % col,index=False)
    return results

# 拼接后序列编码
def ConcatSeq(df,pri_id,cols):
    for i in range(len(cols),1,len(cols)):
        df[cols[0]] += "_" + df[cols[i]]
    df.rename(columns={cols[0]:"%s_target_seq" % cols[0]},inplace=True)
    SeqEncoder(df,pri_id,"%s_target_seq" % cols[0],5)

# 预测
def Predict(model='lgb',encode_type="LabelEncode"):
    df = Base_Process(encode_type)
    # 加一个地理位置聚类
    df = pd.merge(df,_F_Clsuter_Geo(),on=pri_id,how='left')

    # 加一个用户活跃TopN省份 市 区
    temp = _F_GeoCode(n=1)
    df = pd.merge(df,temp,on=pri_id,how='left')

    # 加入distinct的统计
    temp = _F_nunique(3)
    df = pd.merge(df,temp,on=pri_id,how='left')

    # 加入ratio的统计
    temp = _F_nunique_ratio(3)
    df = pd.merge(df,temp,on=pri_id,how='left')

    # 填充缺失值
    df = df.fillna(0)
    _Train = pd.merge(_train,df,on=pri_id,how='left')
    _Test = pd.merge(_test,df,on=pri_id,how='left')

    features = [col for col in _Train.columns if col != pri_id and col != 'y']

    _Label = _Train['y']
    res = pd.DataFrame()
    res[pri_id] = _Test[pri_id]
    pred = _M.Predict(_Train,features,_Label,_Test,model)
    print(pred)
    res['Tag'] = pred
    res.to_csv("resutls.csv",index=False)

# data = Process()
Metric("lgb",'LabelEncode')
Predict()

# 用神经网络预测值作为lgb的特征
# NeuralNetwork()

# 对特征列（count）使用神经网络进行编码，然后编码作为lgb的输入特征
# Encoder('mode')

# 对特征列使用神经网络进行序列编码，然后编码作为lgb的输入特征
# SeqEncoder(op_info,pri_id,'mode',5,5)

# 对merchant, trans_type1, amt_src1拼接做序列编码
# ConcatSeq(trans_info,pri_id,['merchant','trans_type1','amt_src1'])


'''
Write a static function  sqrt t  o compute the square root of a nonnegative number c
given in the input using Newton's method:
- initialize t = c
- replace t with the average of c/t and t
- repeat until desired accuracy reached using condition Math.abs(t - c/t) > epsilon*t
where epsilon =  1e-15
'''

from utils import Util

c = abs(int(input("Enter a non negative number: ")))

root_object = Util()
square_root = root_object.sqrt_newton(c)


import torch
from ultralytics import YOLO
import cv2

def load_yolo_model(weights_path, local=False, version=5):
    print("-----------------------")
    print("Loading YOLOv" + str(version) +  " model...")
    print("-----------------------")
    if version==5:
        if local:
            return torch.hub.load("../yolov5", "custom", source="local", path=weights_path, force_reload=False)
        else:
            return torch.hub.load("ultralytics/yolov5", "custom", path=weights_path, force_reload=False)
    elif version==8:
        return YOLO()

def get_yolo_prediction(img_to_predict, model):
    # Run prediction on the image
    results = model(img_to_predict)
    # Extract the predictions
    boxes = results.xyxy[0].cpu().numpy()
    return boxes

def get_boxes_position(boxes):
    placements = []
    for box in boxes:
        x1, y1, x2, y2, conf, cls = box
        placements.append([int(x1 + (x2 - x1) / 2), int(y2), cls])
    return placements

def draw_yolo_prediction(image, boxes, model):
    for box in boxes:
        x1, y1, x2, y2, conf, cls = box
        conf = float(conf)
        class_name = model.names[int(cls)]
        if conf > 0.5:
            cv2.rectangle(image, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2)
            cv2.putText(image, f"{class_name}: {round(conf, 2)}", (int(x1), int(y1) - 10),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
    return image

def predict_image(img_to_predict, model):
    # Run prediction on the image
    results = model(img_to_predict)
    # Extract the predictions
    boxes = results.xyxy[0].cpu().numpy()
    # Draw the boxes on the image
    placements = []
    for box in boxes:
        x1, y1, x2, y2, conf, cls = box
        placements.append([int(x1 + (x2 - x1)/2), int(y2), cls])
        class_name = model.names[int(cls)]
        if conf > 0.5:
            cv2.rectangle(img_to_predict, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2)
            cv2.putText(img_to_predict, f"{class_name}: {conf}", (int(x1), int(y1) - 10),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
    return img_to_predict, placements

"""
model = load_yolo_model('best4.pt')

img = cv2.imread('ChessBoard\img_test7.png', cv2.IMREAD_COLOR)

predict = predict_image(img, model)

cv2.imshow('Intersections détectées', predict)

# Attendre l'appui d'une touche pour fermer les fenêtres d'affichage
cv2.waitKey(0)
cv2.destroyAllWindows()
"""


from typing import Literal, Optional
import logging
import logging.handlers
import logging.config
import socket
from collections import deque
import pickle
import threading
import multiprocessing as mp
import platform

import pytest
from pytest_lazyfixture import lazy_fixture

def configure_logger():

    LOGGING_CONFIG = {
        "version": 1,
        "disable_existing_loggers": True,
        "formatters": {
            "standard": {
                "format": "%(asctime)s [%(levelname)s] %(name)s: %(message)s",
                "datefmt": "%Y-%m-%d %H:%M:%S",
            },
        },
        "handlers": {
            "console": {
                "level": "DEBUG",
                "formatter": "standard",
                "class": "logging.StreamHandler",
                "stream": "ext://sys.stdout",  # Default is stderr
            },
            "datagram": {
                "level": "DEBUG",
                "formatter": "standard",
                "class": "logging.handlers.DatagramHandler",
                "host": "127.0.0.1",
                "port": 5555
            }
        },
        "loggers": {
            "": {
                "handlers": ["console"],
                "level": "DEBUG",
                "propagate": True,
            },
            "subprocess": {
                "handlers": ["datagram"],
                "level": "DEBUG",
                "propagate": True
            }
        },
    }

    # Setup the logging configuration
    logging.config.dictConfig(LOGGING_CONFIG)

configure_logger()

@pytest.fixture
def logreceiver():

    def listener():

        # Create server and logger to relay messages
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.bind(("127.0.0.1", 5555))
        logger = logging.getLogger("")

        # Continue listening until signaled to stop
        while True:

            # Listen for information
            data = s.recv(4096)
            if data == b"die":
                break

            # Dont forget to skip over the 32-bit length prepended
            logrec = pickle.loads(data[4:])
            rec = logging.LogRecord(
                name=logrec['name'],
                level=logrec['levelno'],
                pathname=logrec['pathname'],
                lineno=logrec['lineno'],
                msg=logrec['msg'],
                args=logrec['args'],
                exc_info=logrec['exc_info'],
                func=logrec['funcName'],

            )
            logger.handle(rec)

        # Safely shutdown socket
        s.close()

    # Start relaying thread
    receiver_thread = threading.Thread(target=listener)
    receiver_thread.start()

    yield

    # Shutting down thread
    t = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    t.sendto(b"die", ("127.0.0.1", 5555))
    receiver_thread.join()

def target_function():

    # Depending on the type of process, get the logger
    if 'Fork' in mp.current_process().name:
        logger = logging.getLogger("")
    elif "Spawn" in mp.current_process().name:
        logger = logging.getLogger("subprocess")
    else:
        raise RuntimeError("Invalid multiprocessing spawn method.")

    # Test logging
    logger.info(f"{mp.current_process().name} - Children Logging")

# ctx = mp.get_context("spawn")
# ctx = mp
class CustomProcess(mp.Process):

    def __init__(self, context: Optional[Literal['spawn', 'fork']] = None):

        # Mapping str to class
        start_to_class_map = {
            "fork": mp.context.ForkProcess,
            "spawn": mp.context.SpawnProcess
        }

        # If no context is given, use the default
        if not context:
            default = mp.get_start_method()
            assert isinstance(default, str)
            self.__class__.__bases__ = (start_to_class_map[default],)
            self._context = default

        else:
            if context in mp.get_all_start_methods():
                self._context = context
                self.__class__.__bases__ = (start_to_class_map[context], )
            else:
                raise RuntimeError(f"Context: `{context}` invalid in {platform.system()}")

        super().__init__(daemon=True)

    def get_logger(self) -> logging.Logger:

        # Depending on the type of process, get the logger
        if self._context == "spawn":
            logger = logging.getLogger("subprocess")
        elif self._context == "fork":
            logger = logging.getLogger("")
        else:
            raise RuntimeError("Invalid multiprocessing spawn method.")

        return logger

    def run(self):

        # Get the logger
        self.logger = self.get_logger()

        # Test logging
        # self.logger.info(f"{mp.current_process().name} - Children Logging")
        self.logger.info(f"{self.__class__.__bases__} - Children Logging")

# ctx = mp
fctx = mp.get_context("fork")
class CustomForkProcess(fctx.Process):

    def get_logger(self) -> logging.Logger:

        # Depending on the type of process, get the logger
        subclass = self.__class__.__bases__[0]
        if subclass == mp.context.ForkProcess or (subclass == mp.context.Process and platform.system() == "Linux"):
            logger = logging.getLogger("")
        elif subclass == mp.context.SpawnProcess or (subclass == mp.context.Process and platform.system() != "Linux"):
            logger = logging.getLogger("subprocess")
        else:
            raise RuntimeError("Invalid multiprocessing spawn method.")

        return logger

    def run(self):

        # Get the logger
        self.logger = self.get_logger()

        # Test logging
        self.logger.info(f"{mp.current_process().name} - Children Logging")

sctx = mp.get_context("spawn")
class CustomSpawnProcess(sctx.Process):

    def get_logger(self) -> logging.Logger:

        # Depending on the type of process, get the logger
        subclass = self.__class__.__bases__[0]
        if subclass == mp.context.ForkProcess or (subclass == mp.context.Process and platform.system() == "Linux"):
            logger = logging.getLogger("")
        elif subclass == mp.context.SpawnProcess or (subclass == mp.context.Process and platform.system() != "Linux"):
            logger = logging.getLogger("subprocess")
        else:
            raise RuntimeError("Invalid multiprocessing spawn method.")

        return logger

    def run(self):

        # Get the logger
        self.logger = self.get_logger()

        # Test logging
        self.logger.info(f"{mp.current_process().name} - Children Logging")

@pytest.mark.parametrize(
    "_logreceiver, ctx",
    [
        (lazy_fixture("logreceiver"), mp.get_context("spawn")),
        (lazy_fixture("logreceiver"), mp.get_context("fork")),

    ]
)
def test_target_function_in_process(_logreceiver, ctx):

    # Try different methods
    logger = logging.getLogger("")
    logger.info("Parent logging")

    ps = []
    for i in range(3):
        p = ctx.Process(target=target_function)
        p.start()
        ps.append(p)

    for p in ps:
        p.join()

    logger.info("Parent logging: end")

def test_spawn_custom_process(logreceiver):

    # Try different methods
    logger = logging.getLogger("")
    logger.info("Parent logging")
    logger.info(CustomSpawnProcess.__bases__)

    ps = []
    for i in range(3):
        p = CustomSpawnProcess(target=target_function)
        p.start()
        ps.append(p)

    for p in ps:
        p.join()

    logger.info("Parent logging: end")

def test_fork_custom_process(logreceiver):

    # Try different methods
    logger = logging.getLogger("")
    logger.info("Parent logging")
    logger.info(CustomSpawnProcess.__bases__)

    ps = []
    for i in range(3):
        p = CustomForkProcess(target=target_function)
        p.start()
        ps.append(p)

    for p in ps:
        p.join()

    logger.info("Parent logging: end")

@pytest.mark.parametrize(
    "_logreceiver, ctx",
    [
        (lazy_fixture("logreceiver"), None),
        (lazy_fixture("logreceiver"), "spawn"),
        (lazy_fixture("logreceiver"), "fork"),

    ]
)
def test_change_custom_process(_logreceiver, ctx):

    # Try different methods
    logger = logging.getLogger("")
    logger.info("Parent logging")

    ps = []
    for i in range(3):
        p = CustomProcess(context=ctx)
        p.start()
        ps.append(p)

    for p in ps:
        p.join()

    logger.info("Parent logging: end")


from openerp import models, fields, api

class MtdApiTokens(models.Model):
    """
    This module is created to store access token, authorisation
    code and refresh_token for each api. Access token expires
    after every 4 hours which need to be refreshed using the
    refresh token. Further, the access token becomes invalid
    after 18 months of it being issued or if revoked manually
    by the user or for any other reason, at which point it
    will need to be reissued by granting authority.
    """
    _name = 'mtd.api_tokens'
    _description = "api token table"

    api_id = fields.Many2one(comodel_name="mtd.api", required=True)
    api_name = fields.Char(related="api_id.name")
    authorisation_code = fields.Char(required=True)
    access_token = fields.Char()
    refresh_token = fields.Char()
    expires_in = fields.Char()
    access_token_recieved_date = fields.Datetime()
    company_id = fields.Char()