import tensorflow as tf
from keras import backend as K
from keras.layers import Dense, Input
from keras.models import Model
from keras.optimizers import Adam
from keras import backend as K
#------------------------------------------------------------------
import socket
import threading
from struct import unpack, pack
from numpy import shape
import numpy as np
from time import sleep
from collections import deque
from Step_8.Parameter import PARA
#------------------------------------------------------------------
import os
import shutil
#------------------------------------------------------------------
import logging
import logging.handlers
logging.basicConfig(filename='./test.log', format='%(asctime)s %(levelname)s %(message)s', level=logging.DEBUG)
#------------------------------------------------------------------
import matplotlib.pyplot as plt
#------------------------------------------------------------------
episode = 0
episode_test = 0
Max_score = 0       # if A3C model get Max_score, A3C model will draw the Max_score grape

class MainModel:
    def __init__(self):
        self._make_folder()
        self.worker = []
        self._make_tensorboaed()
        self.input_para = 6
        self.output_para = 3

        self.actor, self.critic = self.build_model()
        self.optimizer = [self.actor_optimizer(), self.critic_optimizer()]

    def _run(self):
        for i in range(0, 20):
            self.worker.append(A3Cagent(Remote_ip='192.168.0.10',
                                        Remote_port=7100 + i,
                                        CNS_ip='192.168.0.9',
                                        CNS_port=7000 + i,
                                        Shared_actor_net=self.actor,
                                        Shared_cric_net=self.critic,
                                        Optimizer=self.optimizer,
                                        Sess=self.sess,
                                        Summary_ops=[self.summary_op, self.summary_placeholders,
                                                     self.update_ops, self.summary_writer],
                                        Test_model=False,
                                        ))
        # CNS2
        for i in range(0, 20):
            self.worker.append(A3Cagent(Remote_ip='192.168.0.10',
                                        Remote_port=7200 + i,
                                        CNS_ip='192.168.0.7',
                                        CNS_port=7000 + i,
                                        Shared_actor_net=self.actor,
                                        Shared_cric_net=self.critic,
                                        Optimizer=self.optimizer,
                                        Sess=self.sess,
                                        Summary_ops=[self.summary_op, self.summary_placeholders,
                                                     self.update_ops, self.summary_writer],
                                        Test_model=False,
                                        ))
        # CNS3
        for i in range(0, 20):
            self.worker.append(A3Cagent(Remote_ip='192.168.0.10',
                                        Remote_port=7300 + i,
                                        CNS_ip='192.168.0.4',
                                        CNS_port=7000 + i,
                                        Shared_actor_net=self.actor,
                                        Shared_cric_net=self.critic,
                                        Optimizer=self.optimizer,
                                        Sess=self.sess,
                                        Summary_ops=[self.summary_op, self.summary_placeholders,
                                                     self.update_ops, self.summary_writer],
                                        Test_model=False,
                                        ))

        for __ in self.worker:
           __.start()
           sleep(1)
        print("All Agent Start")

        while True:
            sleep(60)
            self._save_model()

    def build_model(self):
        state = Input(batch_shape=(None, self.input_para))
        shared = Dense(8, input_dim=self.input_para, activation='relu', kernel_initializer='glorot_uniform')(state)
        # shared = Dense(48, activation='relu', kernel_initializer='glorot_uniform')(shared)

        actor_hidden = Dense(4, activation='relu', kernel_initializer='glorot_uniform')(shared)
        action_prob = Dense(self.output_para, activation='softmax', kernel_initializer='glorot_uniform')(actor_hidden)

        value_hidden = Dense(2, activation='relu', kernel_initializer='he_uniform')(shared)
        state_value = Dense(1, activation='linear', kernel_initializer='he_uniform')(value_hidden)

        actor = Model(inputs=state, outputs=action_prob)
        critic = Model(inputs=state, outputs=state_value)

        actor._make_predict_function()
        critic._make_predict_function()

        actor.summary(print_fn=logging.info)
        critic.summary(print_fn=logging.info)

        return actor, critic

    def actor_optimizer(self):
        action = K.placeholder(shape=(None, self.output_para))
        advantages = K.placeholder(shape=(None, ))

        policy = self.actor.output

        good_prob = K.sum(action * policy, axis=1)
        eligibility = K.log(good_prob + 1e-10) * K.stop_gradient(advantages)
        loss = -K.sum(eligibility)

        entropy = K.sum(policy * K.log(policy + 1e-10), axis=1)

        actor_loss = loss + 0.01*entropy

        optimizer = Adam(lr=0.01)
        # optimizer = RMSprop(lr=2.5e-4, rho=0.99, epsilon=0.01)
        updates = optimizer.get_updates(self.actor.trainable_weights, [], actor_loss)
        train = K.function([self.actor.input, action, advantages], [], updates=updates)
        return train

    # make loss function for Value approximation
    def critic_optimizer(self):
        discounted_reward = K.placeholder(shape=(None, ))

        value = self.critic.output

        loss = K.mean(K.square(discounted_reward - value))

        optimizer = Adam(lr=0.01)
        # optimizer = RMSprop(lr=2.5e-4, rho=0.99, epsilon=0.01)
        updates = optimizer.get_updates(self.critic.trainable_weights, [], loss)
        train = K.function([self.critic.input, discounted_reward], [], updates=updates)
        return train

    def _save_model(self):
        self.actor.save_weights("./Model/A3C_actor")
        self.critic.save_weights("./Model/A3C_cric")

    def _make_tensorboaed(self):
        self.sess = tf.InteractiveSession()
        K.set_session(self.sess)
        self.sess.run(tf.global_variables_initializer())
        self.summary_placeholders, self.update_ops, self.summary_op = self._setup_summary()
        # tensorboard dir change
        self.summary_writer = tf.summary.FileWriter('./a3c', self.sess.graph)

    def _setup_summary(self):
        episode_total_reward = tf.Variable(0.)
        episode_avg_max_q = tf.Variable(0.)
        episode_duration = tf.Variable(0.)

        tf.summary.scalar('Total Reward/Episode', episode_total_reward)
        tf.summary.scalar('Average Max Prob/Episode', episode_avg_max_q)
        tf.summary.scalar('Duration/Episode', episode_duration)

        summary_vars = [episode_total_reward, episode_avg_max_q, episode_duration]
        summary_placeholders = [tf.placeholder(tf.float32) for _ in range(len(summary_vars))]
        updata_ops = [summary_vars[i].assign(summary_placeholders[i]) for i in range(len(summary_vars))]
        summary_op = tf.summary.merge_all()

        return summary_placeholders, updata_ops, summary_op

    def _make_folder(self):
        fold_list = ['./a3c', './log', './model', './img']
        for __ in fold_list:
            if os.path.isdir(__):
                shutil.rmtree(__)
                sleep(1)
                os.mkdir(__)
            else:
                os.mkdir(__)

class A3Cagent(threading.Thread):
    def __init__(self, Remote_ip, Remote_port, CNS_ip, CNS_port, Shared_actor_net, Shared_cric_net, Optimizer, Sess, Summary_ops, Test_model):
        threading.Thread.__init__(self)
        self.shared_mem_structure = self._make_shared_mem_structure()
        self._init_socket(Remote_ip, Remote_port, CNS_ip, CNS_port)
        self._init_shared_model_setting(Shared_actor_net, Shared_cric_net, Optimizer)
        self._init_input_window_setting()
        self._init_model_information()
        self._init_tensorboard(Sess, Summary_ops)
        self.Test_model = Test_model

    def _init_socket(self, Remote_ip, Remote_port, CNS_ip, CNS_port):
        '''
        :param Remote_ip: 현재 컴퓨터의 ip를 작성하는 곳
        :param Remote_port: 현재 컴퓨터의 port를 의미
        :param CNS_ip: CNS의 ip
        :param CNS_port: CNS의 port
        :return: UDP 통신을 위한 send, resv 소켓 개방 및 정보 반환
        '''
        self.Remote_ip, self.Remote_port = Remote_ip, Remote_port
        self.CNS_ip, self.CNS_port = CNS_ip, CNS_port

        self.resv_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.resv_sock.bind((self.Remote_ip, self.Remote_port))

        self.send_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

        logging.debug('[{}] Initial_socket remote {}/{}, cns {}/{}'.format(self.name,
                                                                           self.Remote_ip, self.Remote_port
                                                                           , self.CNS_ip, self.CNS_port))

    def _init_shared_model_setting(self, Shared_actor_net, Shared_cric_net, Optimizer):
        self.shared_actor_net = Shared_actor_net
        self.shared_cric_net = Shared_cric_net
        self.optimizer = Optimizer

    def _init_input_window_setting(self):
        '''
        입력 윈도우의 창을 설정하는 부분
        :return: list형식의 입력 윈도우
        '''
        self.input_window_shape = self.shared_cric_net.get_input_shape_at(0)

        if PARA.Model == 'LSTM':
            # (none, time-length, parameter) -> 중에서 time-length 를 반환
            self.input_window_box = deque(maxlen=self.shared_cric_net.get_input_shape_at(0)[1])
        elif PARA.Model == 'DNN':
            # (none, time-length, parameter) -> 중에서 time-length 를 반환
            self.input_window_box = deque(maxlen=1)

    def _init_model_information(self):
        self.operation_mode = 0.2

        self.avg_q_max = 0
        self.states, self.actions, self.rewards = [], [], []
        self.action_log, self.reward_log, self.input_window_log = [], [], []
        self.score = 0
        self.step = 0

        self.update_t = 0
        self.update_t_limit = 10

        self.input_dim = 1
        self.input_number = 6

        self.fig = plt.figure(constrained_layout=True)
        self.gs = self.fig.add_gridspec(5, 3)
        self.ax = self.fig.add_subplot(self.gs[:-2, :])
        self.ax_ = self.ax.twinx()
        self.ax2 = self.fig.add_subplot(self.gs[-2, :])
        self.ax3 = self.fig.add_subplot(self.gs[-1, :])

    def _init_tensorboard(self, Sess, Summary_ops):
        self.sess = Sess
        [self.summary_op, self.summary_placeholders, self.update_ops, self.summary_writer] = Summary_ops

    def _make_shared_mem_structure(self):
        # 초기 shared_mem의 구조를 선언한다.
        idx = 0
        shared_mem = {}
        with open('./db.txt', 'r') as f:
            while True:
                temp_ = f.readline().split('\t')
                if temp_[0] == '':  # if empty space -> break
                    break
                sig = 0 if temp_[1] == 'INTEGER' else 1
                shared_mem[temp_[0]] = {'Sig': sig, 'Val': 0, 'Num': idx}
                idx += 1
        # 다음과정을 통하여 shared_mem 은 PID : { type. val, num }를 가진다.
        return shared_mem

    def _update_shared_mem(self):
        '''
        조작 필요 없음
        :return:
        '''
        # binary data를 받아서 보기 쉽게 만들어서 업데이트

        data, addr = self.resv_sock.recvfrom(4008)

        for i in range(0, 4000, 20):
            sig = unpack('h', data[24+i: 26+i])[0]
            para = '12sihh' if sig == 0 else '12sfhh'
            pid, val, sig, idx = unpack(para, data[8+i:28+i])
            pid = pid.decode().rstrip('\x00') # remove '\x00'
            if pid != '':
                self.shared_mem_structure[pid]['Val'] = val

    def _send_control_signal(self, para, val):
        '''
        조작 필요없음
        :param para:
        :param val:
        :return:
        '''
        for i in range(shape(para)[0]):
            self.shared_mem_structure[para[i]]['Val'] = val[i]
        UDP_header = b'\x00\x00\x00\x10\xa8\x0f'
        buffer = b'\x00' * 4008
        temp_data = b''

        # make temp_data to send CNS
        for i in range(shape(para)[0]):
            pid_temp = b'\x00' * 12
            pid_temp = bytes(para[i], 'ascii') + pid_temp[len(para[i]):]  # pid + \x00 ..

            para_sw = '12sihh' if self.shared_mem_structure[para[i]]['Sig'] == 0 else '12sfhh'

            temp_data += pack(para_sw,
                              pid_temp,
                              self.shared_mem_structure[para[i]]['Val'],
                              self.shared_mem_structure[para[i]]['Sig'],
                              self.shared_mem_structure[para[i]]['Num'])

        buffer = UDP_header + pack('h', shape(para)[0]) + temp_data + buffer[len(temp_data):]

        self.send_sock.sendto(buffer, (self.CNS_ip, self.CNS_port))

    # ------------------------------------------------------------------
    # 운전 모드 별 계산
    # ------------------------------------------------------------------

    def _calculator_operation_mode(self):
        tick = self.shared_mem_structure['KCNTOMS']['Val']
        if self.operation_mode == 0.2:      # 0.5%/min
            base_condition = tick / 60000
        elif self.operation_mode == 0.4:    # 1.0%/min
            base_condition = tick / 30000
        elif self.operation_mode == 0.6:    # 1.5%/min
            base_condition = tick / 20000
        elif self.operation_mode == 0.8:    # 2.0%/min
            base_condition = tick / 15000
        else:
            print('Error calculator function')

        upper_condition = base_condition + 0.03
        stady_condition = base_condition + 0.02
        low_condition = base_condition + 0.01
        return upper_condition, stady_condition, low_condition

    def _make_input_window(self):
        power = self.shared_mem_structure['QPROREL']['Val']
        upper_condition, stady_condition, low_condition = self._calculator_operation_mode()

        input_window_temp = [
            power,
            power / 2,
            (upper_condition - power)*10,
            (power - low_condition)*10,
            stady_condition,
            self.operation_mode/10,
        ]
        if len(input_window_temp) != self.input_number:
            logging.error('[{}] _make_input_window ERROR'.format(self))
        self.input_window_box.append(input_window_temp)

        # logging.debug('[{}] input_window_box_shape:{} / input_window_shape_model:{}'.format(self.name,
        #                                                                                np.shape(self.input_window_box),
        #                                                                                self.input_window_shape))

        if PARA.Model == 'LSTM':
            return np.array([self.input_window_box])  # list를 np.array로 전환 (2,3) -> (1, 2, 3)
        elif PARA.Model == 'DNN':
            return np.array(self.input_window_box)  # list를 np.array로 전환 (1, 3) -> (1, 3)

    # ------------------------------------------------------------------
    # CNS 원격 제어 관련
    # ------------------------------------------------------------------
    def _run_cns(self):
        return self._send_control_signal(['KFZRUN'], [3])

    def _set_init_cns(self):
        return self._send_control_signal(['KFZRUN'], [5])

    # ------------------------------------------------------------------
    # gym
    # ------------------------------------------------------------------

    def _gym_send_action_append(self, parameter, value):
        for __ in range(len(parameter)):
            self.para.append(parameter[__])
            self.val.append(value[__])

    def _gym_send_action(self, action):
        power = self.shared_mem_structure['QPROREL']['Val']
        self.para = []
        self.val = []
        if self.shared_mem_structure['KLAMPO22']['Val'] == 0 and power >= 0.10:
            logging.debug('[{}] Tripblock ON'.format(self.name))
            self._gym_send_action_append(['KSWO22', 'KSWO21'], [1, 1])

        # Turbine part
        if power >= 0.04 and self.shared_mem_structure['KBCDO17']['Val'] < 1750:
            logging.debug('[{}] Turbin UP {}/{}'.format(self.name, self.shared_mem_structure['KBCDO17']['Val'],
                                                     self.shared_mem_structure['KBCDO19']['Val']))
            self._gym_send_action_append(['KSWO213'], [1])

        if self.shared_mem_structure['KBCDO17']['Val'] >= 1790:
            logging.debug('[{}] Turbin {}/{}'.format(self.name, self.shared_mem_structure['KBCDO17']['Val'],
                                                        self.shared_mem_structure['KBCDO19']['Val']))
            self._gym_send_action_append(['KSWO213'], [0])

        if power >= 0.04 and self.shared_mem_structure['KBCDO18']['Val'] < 180:
            logging.debug('[{}] Turbin ac UP {}'.format(self.name, self.shared_mem_structure['KBCDO18']['Val']))
            self._gym_send_action_append(['KSWO215'], [1])
        elif self.shared_mem_structure['KBCDO18']['Val'] >= 200:
            self._gym_send_action_append(['KSWO215'], [0])

        if power >= 0.06 and self.shared_mem_structure['KBCDO20']['Val'] < 90:
            self._gym_send_action_append(['KSWO225'], [1])
        elif power >= 0.06 and self.shared_mem_structure['KBCDO20']['Val'] >= 100:
            self._gym_send_action_append(['KSWO225'], [0])

        if power >= 0.06 and self.shared_mem_structure['KBCDO21']['Val'] < 50:
            self._gym_send_action_append(['KSWO227'], [1])
        elif power >= 0.06 and self.shared_mem_structure['KBCDO21']['Val'] >= 60:
            self._gym_send_action_append(['KSWO227'], [0])

        if self.shared_mem_structure['KBCDO17']['Val'] >= 1800 and power >= 0.15:
            self._gym_send_action_append(['KSWO244'], [1])

        if action == 0:
            self._gym_send_action_append(['KSWO33', 'KSWO32'], [0, 0])
        elif action == 1:
            self._gym_send_action_append(['KSWO33', 'KSWO32'], [1, 0])
        elif action == 2:
            self._gym_send_action_append(['KSWO33', 'KSWO32'], [0, 1])

        return self._send_control_signal(self.para, self.val)

    def _gym_reward_done(self):
        power = self.shared_mem_structure['QPROREL']['Val']
        upper_condition, stady_condition, low_condition = self._calculator_operation_mode()

        if self.step >= 600:
            reward = 0
            done = True
        else:
            if power >= low_condition and power <= upper_condition:
                reward = 1
                done = False
            else:
                reward = 0
                done = True

        return reward, done

    def _gym_append_sample(self, input_window, policy, action, reward):
        if PARA.Model == 'LSTM':
            self.states.append(input_window[0]) # (1, 2, 3) -> (2, 3) 잡아서 추출
        elif PARA.Model == 'DNN':
            self.states.append(input_window)  # (1, 2, 3) -> (2, 3) 잡아서 추출
        act = np.zeros(np.shape(policy)[0])
        act[action] = 1
        self.actions.append(act)
        self.rewards.append(reward)

    def _gym_predict_action(self, input_window):
        # policy = self.local_actor_model.predict(input_window)[0]
        policy = self.shared_actor_net.predict(np.reshape(input_window, [self.input_dim, self.input_number]))[0]
        if self.Test_model:
            # 검증 네트워크의 경우 결과를 정확하게 뱉음
            action = np.argmax(policy)
        else:
            # 훈련 네트워크의 경우 랜덤을 값을 뱉음.
            action = np.random.choice(np.shape(policy)[0], 1, p=policy)[0]
        self.avg_q_max += np.amax(self.shared_actor_net.predict(np.reshape(input_window, [self.input_dim,
                                                                                self.input_number])))
        return policy, action

    def _gym_draw_img(self, max_score_ep, current_ep):
        self.ax.clear()
        self.ax_.clear()
        self.ax2.clear()
        self.ax3.clear()

        power, low, high, action = [], [], [], []

        for __ in range(len(self.interval_log)):
            power.append((self.input_window_log[__][0]*100))
            high.append((self.input_window_log[__][0] + self.input_window_log[__][2]/10)*100)
            low.append((self.input_window_log[__][0] - self.input_window_log[__][3]/10)*100)
            if self.action_log[__] == 0:
                action.append(0)
            elif self.action_log[__] == 1:
                action.append(1)
            elif self.action_log[__] == 2:
                action.append(-1)

        # self.turbin_log = {'Setpoint': [], 'Real': [], 'Electric': []}

        self.ax.plot(self.interval_log, power, 'g')
        self.ax.plot(self.interval_log, low, 'r')
        self.ax.plot(self.interval_log, high, 'b')

        self.ax_.plot(self.interval_log, self.turbin_log['Electric'], 'black')

        self.ax.set_ylabel('Reactor Power [%]')
        self.ax_.set_ylabel('Electrical Power [MWe]')
        self.ax.set_ylim(bottom=0)
        self.ax_.set_ylim(bottom=0)
        self.ax.grid()

        self.ax2.plot(self.interval_log, action)
        self.ax2.set_yticks((-1, 0, 1))
        self.ax2.set_yticklabels(('Out', 'Stay', 'In'))
        self.ax2.set_ylabel('Rod control')
        self.ax2.grid()

        self.ax3.plot(self.interval_log, self.turbin_log['Setpoint'], 'r')
        self.ax3.plot(self.interval_log, self.turbin_log['Real'], 'b')
        self.ax3.set_yticks((900, 1800))
        self.ax3.grid()
        self.ax3.set_yticklabels(('900', '1800'))
        self.ax3.set_ylabel('Turbine RPM')
        self.ax3.set_xlabel('Time [s]')

        self.fig.savefig(fname='./img/{}_{}_{}.png'.format(current_ep-1, max_score_ep, self.name), dpi=600, facecolor=None)

    def _gym_save_control_logger(self, input_window, action, reward):
        self.interval_log.append(self.interval)
        self.interval += 1
        self.action_log.append(action)
        self.input_window_log.append(input_window)

        self.turbin_log['Setpoint'].append(self.shared_mem_structure['KBCDO17']['Val'])
        self.turbin_log['Real'].append(self.shared_mem_structure['KBCDO19']['Val'])
        self.turbin_log['Electric'].append(self.shared_mem_structure['KBCDO22']['Val'])

        self.reward_log.append(reward)

    def _gym_save_control_history(self):
        if self.Test_model:
            with open('./log/Test_control_history_{}_{}.txt'.format(episode_test, self.name), 'a') as f:
                for __ in range(len(self.action_log)):
                    f.write('{}, {}, {}, {}, '.format(self.name,
                                                      self.interval_log[__],
                                                      self.reward_log[__],
                                                      self.action_log[__]))
                    for _ in self.input_window_log[__]:
                        f.write('{}, '.format(self.input_window_log[__][_]))
                    f.write('\n')
        else:
            with open('./log/Control_history_{}_{}.txt'.format(episode, self.name), 'a') as f:
                for __ in range(len(self.action_log)):
                    f.write('{}, {}, {}, {}, '.format(self.name,
                                                      self.interval_log[__],
                                                      self.reward_log[__],
                                                      self.action_log[__]))
                    for _ in self.input_window_log[__]:
                        f.write('{}, '.format(_))
                    f.write('\n')

    def _gym_save_score_history(self):
        if self.Test_model:
            # 검증 네트워크에서 결과 값을 저장함
            with open('./test_history.txt', 'a') as f:
                f.writelines('{}/{}/{}\n'.format(episode_test, self.name, self.score))
        else:
            # 훈련 네트워크에서 결과 값을 저장함
            with open('./history.txt', 'a') as f:
                f.writelines('{}/{}/{}\n'.format(episode, self.name, self.score))

    # ------------------------------------------------------------------
    # 네트워크 훈련 관련
    # ------------------------------------------------------------------

    def discount_rewards(self, rewards, done=True):
        discounted_rewards = np.zeros_like(rewards)
        running_add = 0
        if not done:
            running_add = self.shared_cric_net.predict(np.reshape(self.states[-1], (self.input_dim,
                                                                                self.input_number)))[0]
        for t in reversed(range(0, len(rewards))):
            running_add = running_add * 0.99 + rewards[t]
            discounted_rewards[t] = running_add
        return discounted_rewards

    # update policy network and value network every episode
    def train_episode(self, done):
        discounted_rewards = self.discount_rewards(self.rewards, done)

        values = self.shared_cric_net.predict(np.array(self.states))
        values = np.reshape(values, len(values))

        advantages = discounted_rewards - values
        print(np.shape(self.states), np.shape(self.actions), np.shape(advantages), )
        self.optimizer[0]([self.states, self.actions, advantages])
        self.optimizer[1]([self.states, discounted_rewards])
        self.states, self.actions, self.rewards = [], [], []
    # ------------------------------------------------------------------
    # 기타 편의 용
    # ------------------------------------------------------------------

    def _add_function_routine(self, input_window, action):
        reward = 1
        self.score += reward
        self.step += 1
        self.update_t += 1
        self._gym_save_control_logger(input_window[0], action, reward)
        input_window = self._make_input_window()
        if PARA.show_input_windows:
            logging.debug('[{}] Input_window {}'.format(self.name, input_window))
        self._gym_send_action(action)
        self._run_cns()
        return input_window

    # ------------------------------------------------------------------
    def run(self):
        if self.Test_model:
            global episode_test
        else:
            global episode

        global Max_score
        self.Max_score = Max_score

        logging.debug('[{}] Start socket'.format(self.name))
        self.action_log, self.reward_log, self.input_window_log, self.interval_log, self.interval= [], [], [], [], 0
        self.turbin_log = {'Setpoint': [], 'Real': [], 'Electric': []}
        #
        # CNS_10_21.tar 기반의 CNS에서 구동됨.
        #
        self._set_init_cns()
        sleep(1)
        mode = 0
        while episode < 10000:
            self._update_shared_mem()
            if mode == 0:
                if self.shared_mem_structure['KCNTOMS']['Val'] < 10:
                    mode += 1
            elif mode == 1: # LSTM의 데이터를 쌓기 위해서 대기 하는 곳
                if self.shared_mem_structure['KFZRUN']['Val'] == 6:
                    self._run_cns()
                if self.shared_mem_structure['KFZRUN']['Val'] == 4:
                    input_window = self._make_input_window()
                    if np.shape(input_window)[1] == self.input_number:
                        mode += 1
                    else:
                        self._run_cns()
            elif mode == 2: # 좀 더 대기하는 곳
                if self.shared_mem_structure['KFZRUN']['Val'] == 4:
                    if self.shared_mem_structure['KCNTOMS']['Val'] > 15:
                        input_window = self._make_input_window()
                        mode += 1
                    else:
                        input_window = self._make_input_window()
                        self._run_cns()
            elif mode == 3: # 초기 액션
                if self.shared_mem_structure['KFZRUN']['Val'] == 4:
                    input_window = self._make_input_window()
                    # 2.1 네트워크 액션 예측
                    policy, action = self._gym_predict_action(input_window[0]) #(4,)
                    # 2.2. 액션 전송
                    self._gym_send_action(action)
                    self._run_cns()
                    mode += 1
            elif mode == 4:
                if self.shared_mem_structure['KFZRUN']['Val'] == 4:
                    # 2.4 t+1초의 상태에 대한 보상 검증
                    reward, done = self._gym_reward_done()
                    if reward == 100:
                        self.score += 1
                    else:
                        self.score += reward

                    self.step += 1
                    self.update_t += 1

                    # 2.5 data box 에 append
                    self._gym_append_sample(input_window[0], policy, action, reward)
                    self._gym_save_control_logger(input_window[0], action, reward)
                    if PARA.save_input_log:
                        logging.debug('[{}] input window\n{}'.format(self.name, input_window[0]))

                    if self.update_t > self.update_t_limit and done != True:
                        self.train_episode(self.step != 351)
                        self.update_t = 0
                    else:
                        pass

                    # 2.5.2 죽으면 정보 호출 및 텐서보드 업데이트
                    if done:
                        # 운전 이력 저장
                        self._gym_save_score_history()

                        if self.Test_model:
                            pass
                            episode_test += 1
                            self._gym_save_control_history()
                            self.action_log, self.reward_log, self.input_window_log, self.interval_log, self.interval = [], [], [], [], 0
                            self.turbin_log = {'Setpoint': [], 'Real': [], 'Electric': []}
                            print("[TEST]{} Test_Episode:{}, Score:{}, Step:{}".format(episode_test, self.name, self.score,
                                                                                self.step))
                            # if episode_test % 10 == 1:
                            #     logging.debug('[{}] [TEST] Shared_net_work update'.format(self.name))
                            #     # test의 네트워크를 업데이트
                            #     # self.local_actor_model.set_weights(self.shared_actor_net.get_weights())
                            #     # self.local_cric_model.set_weights(self.shared_cric_net.get_weights())
                            #     self.local_actor_model.load_weights('./Model/A3C_actor')
                            #     self.local_cric_model.load_weights('./Model/A3C_cric')
                            #     # test의 네트워크를 저장
                            #     self.local_actor_model.save_weights("./Test_Model/{}_A3C_actor".format(episode))
                            #     self.local_cric_model.save_weights("./Test_Model/{}_A3C_cric".format(episode))
                            #     self.states, self.actions, self.rewards = [], [], []

                        else:
                            episode += 1
                            self.train_episode(self.step != 601)
                            self._gym_save_control_history()

                            if self.score >= Max_score:
                                self._gym_draw_img(current_ep=episode, max_score_ep=self.score)
                                Max_score = self.score
                                self.Max_score = Max_score

                            self.action_log, self.reward_log, self.input_window_log, self.interval_log, self.interval = [], [], [], [], 0
                            self.turbin_log = {'Setpoint': [], 'Real': [], 'Electric': []}
                            print("[TRAIN]{} Episode:{}, Score:{}, Step:{}".format(episode, self.name, self.score,
                                                                                self.step))

                            if self.score >= 120:
                                self.score = 120
                            else:
                                pass

                            stats = [self.score, self.avg_q_max/(self.step/4), self.step]
                            for i in range(len(stats)):
                                self.sess.run(self.update_ops[i], feed_dict={self.summary_placeholders[i]:
                                                                                float(stats[i])})
                            summary_str = self.sess.run(self.summary_op)
                            self.summary_writer.add_summary(summary_str, episode + 1)

                        self.avg_q_max, self.score = 0, 0
                        self.step = 0
                        self.update_t = 0

                        mode += 5
                        done = False
                    else:
                        # 2.6 액션의 결과를 토대로 다시 업데이트
                        input_window = self._make_input_window()
                        if PARA.show_input_windows:
                            logging.debug('[{}] Input_window {}'.format(self.name, input_window))
                        # 2.1 네트워크 액션 예측
                        policy, action = self._gym_predict_action(input_window[0])  # (4,)
                        # 2.2. 액션 전송
                        self._gym_send_action(action)
                        self._run_cns()
                        mode += 1

            if mode == 5 or mode == 6 or mode == 7:
                if self.shared_mem_structure['KFZRUN']['Val'] == 4:
                    input_window = self._add_function_routine(input_window, action)
                    mode += 1
            if mode == 8:
                if self.shared_mem_structure['KFZRUN']['Val'] == 4:
                    input_window = self._add_function_routine(input_window, action)
                    mode -= 4
            if mode == 9:
                if self.shared_mem_structure['KFZRUN']['Val'] == 6:
                    self._run_cns()
                    mode = 0
                else:
                    self._set_init_cns()
    # ------------------------------------------------------------------

if __name__ == '__main__':
    test = MainModel()
    test._run()


from backend.db import db
from sqlalchemy import text, ForeignKey
from marshmallow import Schema, fields
from sqlalchemy.orm import relationship

class Tax(db.Model):
    __tablename__ = 'taxes'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(255), nullable=True)
    initial_date = db.Column(db.Date, nullable=True)
    final_date = db.Column(db.Date, nullable=True)
    purchase_pct = db.Column(db.Float, default=0)
    sale_pct = db.Column(db.Float, default=0)
    account_id = db.Column(db.Integer, ForeignKey("accounts.id"))
    installation_id = db.Column(db.Integer, default=0)
    created_at = db.Column(db.TIMESTAMP, nullable=True, server_default=text('CURRENT_TIMESTAMP'))
    updated_at = db.Column(db.TIMESTAMP, nullable=True, server_default=text('CURRENT_TIMESTAMP'))

    account = relationship("Account", foreign_keys=[account_id])

    @property
    def account_name(self):
        if self.account is not None:
            return self.account.code
        return None

class TaxSchema(Schema):
    id = fields.Number()
    name = fields.Str()
    account_name = fields.Str()
    initial_date = fields.Date()
    final_date = fields.Date()
    purchase_pct = fields.Number()
    sale_pct = fields.Number()
    account_id = fields.Number()
    installation_id = fields.Number()
    created_at = fields.DateTime()
    updated_at = fields.DateTime()


import collections
class Solution:
    def maxCandies(self, status: List[int], candies: List[int], keys: List[List[int]], containedBoxes: List[List[int]], initialBoxes: List[int]) -> int:
        num = 0
        Q = collections.deque([box for box in initialBoxes if status[box] == 1])
        possible = set([box for box in initialBoxes if status[box] == 0])
        while Q:
            box = Q.popleft()
            num += candies[box]
            for key in keys[box]:
                status[key] = 1
                if key in possible:
                    possible.remove(key)
                    Q.append(key)

            for cb in containedBoxes[box]:
                if status[cb] == 1:
                    Q.append(cb)
                else:
                    possible.add(cb)

        return num

1201-1300/1298-Maximum Candies You Can Get from Boxes/1298-Maximum Candies You Can Get from Boxes.py


""" 4. La gestión de un concesionario de coches está informatizada porque
disponen de una gran cartera de clientes y un gran stock.
Para facilitar las búsquedas en la tabla de stock, los vehículos se encuentran ordenados por matrícula,
de cada vehículo nos interesa la matrícula, marca, modelo y precio,
los nuevos vehículos se insertan en su posición correspondiente.
El sistema también almacena los datos de los clientes que se han interesado por un vehículo,
de cada cliente se almacena nombre, apellidos, dirección y teléfono.
Un cliente puede estar interesado en varios coches (o ninguno aún),
y por un coche se pueden interesar varios clientes (o ninguno).
Cuando un coche se vende, desaparece del stock, sin embargo,
los datos de un cliente nunca desaparecen de sistema y su información se almacena en orden de aparición.
El 18 de septiembre de 2000 Tráfico instauró el actual sistema de matrículas,
que constan de cuatro dígitos y tres letras consonantes (suprimiéndose las cinco vocales, y las letras Ñ y Q).
Las estructuras de datos diseñadas para la aplicación son:"""

from recordclass import recordclass

tCoche = recordclass('Coche', 'matricula marca modelo precio')
#tStock = lista [variable] de tCoche ordenados alfabéticamente
tCliente = recordclass('Cliente', 'nombre, apellidos, direccion, telefono')
#tClientes = lista[variable] de tCliente
tInteresado = recordclass('Interesado', 'coche, usuario')
	#coche es una matrícula, usuario es su posición en la lista de clientes.
#tInteresados = lista[variable] de tInteresado

# A. (3 puntos): Haga una función que localice y devuelva un coche del stock conocida su matrícula.
#Debe emplear el algoritmo más eficiente posible y justificar su elección.

def busqueda_matricula(stock, matricula):
    inicio = 0
    fin = len(stock)-1

    while inicio <= fin:
        mitad = (inicio + fin) // 2
        coche = stock[mitad]

        if matricula == coche.matricula:
            return mitad
        if matricula < coche.matricula:
            fin = mitad - 1
        else: inicio = mitad + 1
    return None

# PROBADOR

stock = [tCoche(matricula='4562CSD', marca='Toyota', modelo='Corolla', precio=25000),
        tCoche(matricula='8095HGM', marca='Ford', modelo='Mustang', precio=45000),
        tCoche(matricula='8156DFB', marca='Honda', modelo='Civic', precio=22000),
        tCoche(matricula='9156GGB', marca='Chevrolet', modelo='Camaro', precio=48000),
        tCoche(matricula='9959YJN', marca='Volkswagen', modelo='Golf', precio=20000)]

print(busqueda_matricula(stock, '4562CSD'), 0)
print(busqueda_matricula(stock, '8156DFB'), 2)
print(busqueda_matricula(stock, '9959YJN'), 4)
print(busqueda_matricula(stock, '1111YJN'), 'None')

# B. (2 puntos): Diseñe un algoritmo recursivo que, dada la lista de interesados y una matrícula,
#muestre los datos de cada cliente interesado en ese vehículo.

clientes = [tCliente('JUAN', 'GARCIA', 'CALLE X', '972833829'),
            tCliente('MARIA', 'FERNANDEZ', 'CALLE Y', '8574185861'),
            tCliente('PEPE', 'GONZALEZ', 'CALLE Z', '8916513796'),
            tCliente('FRAN', 'LOPEZ', 'CALLE R', '308U3241'),
            tCliente('LUCIA', 'MARTINEZ', 'CALLE T', '463241232')]

interesados = [tInteresado('4562CSD', 0),
               tInteresado('8095HGM', 1),
               tInteresado('9959YJN', 2),
               tInteresado('4562CSD', 3),
               tInteresado('8156DFB', 4)]

def mostrar_interesados(lista, matricula, index = 0):
    if index < len(lista):

        if lista[index].coche == matricula:
            print(clientes[index])

        mostrar_interesados(lista, matricula, index + 1)

# PROBADOR

print(mostrar_interesados(interesados, '9959YJN'))
print(mostrar_interesados(interesados, '8888YJN'))

# C. (2 puntos): Haga una función que reciba una cadena de texto y devuelva si es una matrícula válida o no.

def matricula_valida(matricula):
    matricula = matricula.upper()

    if len(matricula) != 7: # TIENE QUE TENER 7 CARACTERES
        return False

    contador_letras = 0
    contador_numeros = 0

    letras_posibles = 'WRTYPSDFGHJKLZXCVBNM'
    numeros_posibles = '0123456789'

    for i in range(0, 4): # LAS CUATRO PRIMEROS CARACTERES TIENEN QUE SER NUMEROS
        if matricula[i] in numeros_posibles:
            contador_numeros +=1

    for i in range(4, 7): # LOS ULTIMOS TRES CARACTERES TIENEN QUE SER LETRAS
        if matricula[i] in letras_posibles:
            contador_letras += 1

    return contador_letras == 3 and contador_numeros == 4

# PROBADOR

print(matricula_valida('4545YJN'), 'TRUE')
print(matricula_valida('YJN4545'), 'FALSE')
print(matricula_valida('45455JN'), 'FALSE')
print(matricula_valida('4545AJN'), 'FALSE')
print(matricula_valida('4545FAJN'), 'FALSE')

# D. (3 puntos): Haga un subprograma que devuelve una matrícula solicitada al usuario (pedida tantas veces como sea necesario).

def matricula_pedida(msg):
    a = input(msg)
    if not matricula_valida(a):
        a = input(msg)
    return a.upper()

# PROBADOR

print(matricula_pedida('INTRODUCE UNA MATRICULA: '))


"""The top level of the ASN.1 tools package contains commonly used
functions and classes, and the command line interface.

"""

import sys
import os
import argparse
import binascii
import logging

from prompt_toolkit.contrib.completers import WordCompleter
from prompt_toolkit import prompt
from prompt_toolkit.history import FileHistory
from prompt_toolkit.interface import AbortAction
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory

from .compiler import compile_dict
from .compiler import compile_string
from .compiler import compile_files
from .compiler import pre_process_dict
from .parser import parse_string
from .parser import parse_files
from .parser import ParseError
from .errors import EncodeError
from .errors import DecodeError
from .errors import CompileError

__author__ = 'Erik Moqvist'
__version__ = '0.64.0'

class ArgumentParserError(Exception):
    pass

class ArgumentParser(argparse.ArgumentParser):

    def exit(self, status=0, message=None):
        if message:
            self._print_message(message, sys.stdout)

        raise ArgumentParserError(message)

    def error(self, message):
        self.print_usage(sys.stdout)
        message = '{}: error: {}'.format(self.prog, message)
        print(message)

        raise ArgumentParserError(message)

def _convert_hexstring(input_spec,
                       output_spec,
                       output_format,
                       type_name,
                       hexstring):
    try:
        encoded = binascii.unhexlify(hexstring)
    except Exception as e:
        raise TypeError("'{}': {}".format(hexstring, str(e)))

    decoded = input_spec.decode(type_name, encoded)

    if output_format in ['asn1', 'xer', 'jer']:
        decoded = output_spec.encode(type_name, decoded, indent=4).strip()
    else:
        decoded = binascii.hexlify(output_spec.encode(type_name, decoded))

    print(decoded.decode('latin-1'))

def _compile_files(specifications, input_format, output_format):
    parsed = parse_files(specifications)
    input_spec = compile_dict(parsed, input_format)
    output_spec = compile_dict(parsed, output_format)

    return input_spec, output_spec

def _do_convert(args):
    input_spec, output_spec = _compile_files(args.specification,
                                             args.input_format,
                                             args.output_format)

    if args.hexstring == '-':
        for hexstring in sys.stdin:
            hexstring = hexstring.strip('\r\n')

            if hexstring:
                try:
                    _convert_hexstring(input_spec,
                                       output_spec,
                                       args.output_format,
                                       args.type,
                                       hexstring)
                except TypeError:
                    print(hexstring)
                except DecodeError as e:
                    print(hexstring)
                    print(str(e))
            else:
                print(hexstring)
    else:
        _convert_hexstring(input_spec,
                           output_spec,
                           args.output_format,
                           args.type,
                           args.hexstring)

def _handle_command_compile(line):
    parser = ArgumentParser(prog='compile')
    parser.add_argument('-i', '--input-format',
                        choices=('ber', 'der', 'jer', 'per', 'uper', 'xer'),
                        default='ber',
                        help='Input format (default: ber).')
    parser.add_argument('-o', '--output-format',
                        choices=('ber', 'der', 'jer', 'per', 'uper', 'xer', 'asn1'),
                        default='asn1',
                        help='Output format (default: asn1).')
    parser.add_argument('specification',
                        nargs='+',
                        help='ASN.1 specification as one or more .asn files.')

    try:
        args = parser.parse_args(args=line.split()[1:])
    except ArgumentParserError:
        return None, None, None

    try:
        input_spec, output_spec = _compile_files(args.specification,
                                                 args.input_format,
                                                 args.output_format)
        return input_spec, output_spec, args.output_format
    except Exception as e:
        print('error: {}'.format(str(e)))
        return None, None, None

def _handle_command_convert(line, input_spec, output_spec, output_format):
    if not input_spec:
        print("No compiled specification found. Please use the "
              "'compile' command to compile one.")
        return

    parser = ArgumentParser(prog='convert')
    parser.add_argument('type', help='Type to convert.')
    parser.add_argument(
        'hexstring',
        help='Hexstring to convert.')

    try:
        args = parser.parse_args(args=line.split()[1:])
    except ArgumentParserError:
        return

    try:
        _convert_hexstring(input_spec,
                           output_spec,
                           output_format,
                           args.type,
                           args.hexstring)
    except Exception as e:
        print('error: {}'.format(str(e)))

def _handle_command_help():
    print('Commands:')
    print('  compile')
    print('  convert')

def _do_shell(_args):
    commands = ['compile', 'convert', 'help', 'exit']
    completer = WordCompleter(commands, WORD=True)
    user_home = os.path.expanduser('~')
    history = FileHistory(os.path.join(user_home, '.asn1tools-history.txt'))
    input_spec = None
    output_spec = None
    output_format = None

    print("\nWelcome to the asn1tools shell!\n")

    while True:
        try:
            line = prompt(u'$ ',
                          completer=completer,
                          complete_while_typing=True,
                          auto_suggest=AutoSuggestFromHistory(),
                          enable_history_search=True,
                          history=history,
                          on_abort=AbortAction.RETRY)
        except EOFError:
            return

        line = line.strip()

        if line:
            if line.startswith('compile'):
                input_spec, output_spec, output_format = _handle_command_compile(line)
            elif line.startswith('convert'):
                _handle_command_convert(line,
                                        input_spec,
                                        output_spec,
                                        output_format)
            elif line == 'help':
                _handle_command_help()
            elif line == 'exit':
                return
            else:
                print('{}: command not found'.format(line))

def _main():
    parser = argparse.ArgumentParser(
        description='Various ASN.1 utilities.')

    parser.add_argument('-d', '--debug', action='store_true')
    parser.add_argument('-v', '--verbose',
                        choices=(0, 1, 2),
                        type=int,
                        default=1,
                        help=('Control the verbosity; disable(0), warning(1) '
                              'and debug(2). (default: 1).'))
    parser.add_argument('--version',
                        action='version',
                        version=__version__,
                        help='Print version information and exit.')

    # Workaround to make the subparser required in Python 3.
    subparsers = parser.add_subparsers(title='subcommands',
                                       dest='subcommand')
    subparsers.required = True

    # The 'convert' subparser.
    subparser = subparsers.add_parser(
        'convert',
        description='Convert given hextring and print it to standard output.')
    subparser.add_argument('-i', '--input-format',
                           choices=('ber', 'der', 'jer', 'per', 'uper', 'xer'),
                           default='ber',
                           help='Input format (default: ber).')
    subparser.add_argument('-o', '--output-format',
                           choices=('ber', 'der', 'jer', 'per', 'uper', 'xer', 'asn1'),
                           default='asn1',
                           help='Output format (default: asn1).')
    subparser.add_argument('specification',
                           nargs='+',
                           help='ASN.1 specification as one or more .asn files.')
    subparser.add_argument('type', help='Type to convert.')
    subparser.add_argument(
        'hexstring',
        help='Hexstring to convert, or - to read hexstrings from standard input.')
    subparser.set_defaults(func=_do_convert)

    # The 'shell' subparser.
    shell_parser = subparsers.add_parser('shell',
                                         description='An interactive shell.')
    shell_parser.set_defaults(func=_do_shell)

    args = parser.parse_args()

    levels = [logging.CRITICAL, logging.WARNING, logging.DEBUG]
    level = levels[args.verbose]

    logging.basicConfig(format='%(levelname)s: %(message)s',
                        level=level)

    if args.debug:
        args.func(args)
    else:
        try:
            args.func(args)
        except BaseException as e:
            sys.exit('error: {}'.format(str(e)))


import pytorch_lightning as pl
import torch
import torchmetrics
import transformers

from sdg_clf import base

class LitSDG(pl.LightningModule):
    def __init__(self, model: transformers.AutoModelForSequenceClassification, hparams: base.HParams):
        super().__init__()
        self.model = model
        self.HParams = hparams
        self.criterion = torch.nn.BCEWithLogitsLoss()
        self.sigmoid = torch.nn.Sigmoid()
        # Metrics
        self.train_accuracy = torchmetrics.Accuracy(subset_accuracy=True, num_classes=17, multiclass=False)
        self.train_micro_precision = torchmetrics.Precision(num_classes=17, multiclass=False, average="micro")
        self.train_micro_recall = torchmetrics.Recall(num_classes=17, multiclass=False, average="micro")
        self.train_micro_f1 = torchmetrics.F1Score(num_classes=17, multiclass=False, average="micro")
        self.train_macro_precision = torchmetrics.Precision(num_classes=17, multiclass=False, average="macro")
        self.train_macro_recall = torchmetrics.Recall(num_classes=17, multiclass=False, average="macro")
        self.train_macro_f1 = torchmetrics.F1Score(num_classes=17, multiclass=False, average="macro")
        self.val_accuracy = torchmetrics.Accuracy(subset_accuracy=True, num_classes=17, multiclass=False)
        self.val_micro_precision = torchmetrics.Precision(num_classes=17, multiclass=False, average="micro")
        self.val_micro_recall = torchmetrics.Recall(num_classes=17, multiclass=False, average="micro")
        self.val_micro_f1 = torchmetrics.F1Score(num_classes=17, multiclass=False, average="micro")
        self.val_macro_precision = torchmetrics.Precision(num_classes=17, multiclass=False, average="macro")
        self.val_macro_recall = torchmetrics.Recall(num_classes=17, multiclass=False, average="macro")
        self.val_macro_f1 = torchmetrics.F1Score(num_classes=17, multiclass=False, average="macro")

    def forward(self, input_ids: torch.Tensor, attention_mask: torch.Tensor) -> torch.Tensor:
        return self.model(input_ids, attention_mask=attention_mask).logits

    def training_step(self, batch, batch_idx):
        input_ids = batch["input_ids"]
        attention_mask = batch["attention_mask"]
        labels = batch["label"]
        model_outputs = self(input_ids, attention_mask=attention_mask)
        loss = self.criterion(model_outputs, labels.float())
        preds = self.sigmoid(model_outputs)
        self.train_accuracy(preds, labels)
        self.train_micro_precision(preds, labels)
        self.train_micro_recall(preds, labels)
        self.train_micro_f1(preds, labels)
        self.train_macro_precision(preds, labels)
        self.train_macro_recall(preds, labels)
        self.train_macro_f1(preds, labels)
        metrics_dict = {"train_loss": loss, "train_accuracy": self.train_accuracy,
                        "train_micro_precision": self.train_micro_precision,
                        "train_micro_recall": self.train_micro_recall,
                        "train_micro_f1": self.train_micro_f1, "train_macro_precision": self.train_macro_precision,
                        "train_macro_recall": self.train_macro_recall, "train_macro_f1": self.train_macro_f1}
        self.log_dict(metrics_dict)

        return loss

    def validation_step(self, batch, batch_idx):
        input_ids = batch["input_ids"]
        attention_mask = batch["attention_mask"]
        labels = batch["label"]
        model_outputs = self(input_ids, attention_mask=attention_mask)
        loss = self.criterion(model_outputs, labels.float())
        preds = self.sigmoid(model_outputs)
        self.val_accuracy(preds, labels)
        self.val_micro_precision(preds, labels)
        self.val_micro_recall(preds, labels)
        self.val_micro_f1(preds, labels)
        self.val_macro_precision(preds, labels)
        self.val_macro_recall(preds, labels)
        self.val_macro_f1(preds, labels)
        metrics_dict = {"val_loss": loss, "val_accuracy": self.val_accuracy, "val_micro_precision": self.val_micro_precision,
                        "val_micro_recall": self.val_micro_recall,
                        "val_micro_f1": self.val_micro_f1, "val_macro_precision": self.val_macro_precision,
                        "val_macro_recall": self.val_macro_recall, "val_macro_f1": self.val_macro_f1}
        self.log_dict(metrics_dict)

    def configure_optimizers(self):
        return torch.optim.AdamW(self.parameters(), lr=self.HParams.lr, weight_decay=self.HParams.weight_decay)


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Module for reading and processing CLM vector output, specifically for multiple
elevation class (MEC) output over glaciated landunits

LIMITATIONS
Currently only files with single time steps are supported
Files with multiple time records may work, but are untested!!!

@author: L.vankampenhout@uu.nl
"""
import sys
import numpy as np
from netCDF4 import Dataset, default_fillvals
from scipy.interpolate import InterpolatedUnivariateSpline

GLC_NEC = 10 # maximum number of elevation classes present in input file
COLUNIT_GLCMEC = 4 # for landunit types and column types, land ice = 7 (older CLM) land ice = 4 (newer CLM)

rtnnam = lambda: sys._getframe(1).f_code.co_name # helper function that queries name of current routine

class VectorMecVariable(object):
   """
   This class represents a memory representation of a CLM variable which has been
   computed on Multiple Elevation Classes (MEC).

   Normally, these variables are aggregated  on the grid cell level, and the vertical
   or level information is lost.
   However, by setting hist_dov2xy = .false. in the CLM namelist all CLM columns are
   output -- this is called "vector output".

   MEC variables are used over ice sheets and glaciers and typically have
   11 levels: level 0 representing tundra and the other 10 are different elevations
   of the glaciated part of the grid cell.

   Some of these levels are "virtual", meaning that they don't have any area associated
   with them. Over Antarctica, virtual columns are usually disabled. This is controlled
   by the CLM namelist (see User Documentation for more details).
   To find out which columns are virtual and which are not, coupler output can be used.
   This coupler information is required to e.g. downscale on a 2 D map.

   MEC column area and topographic height are determined by a high-resolution ice mask,
   e.g. from CISM. This is typically done in the first time step of any simulation.
   The topographic height of MEC levels is variable across grid cells. For example,
   in one grid cell, level 6 may correspond to elevation 550 m, whereas in the next it
   corresponds to elevation 570 m. This stems from the fact that MEC columns are associated
   with *bins* and not *heights*. Within each elevation bin, the mean of the underlying
   topography is assigned to topographic height (CLM: variable TOPO_COL).
   """

   def __init__(self, varname, fname_vector, fname_vecinfo = None):
      """
      init and read MEC variable into memory

      By default, the vector information is read from the vector file itself.
      In case this is not possible, e.g. when that file has been post-processed, an
      auxiliary file can be given from which this data is retrieved.

      :param varname:         CLM variable name
      :param fname_vector:    filename of CLM vector file (e.g. XXX.h2.YYY.nc)
      :param fname_vecinfo:   filename of CLM vector grid info file (optional)
      :type varname:          string
      :type fname_vector:     string
      :type fname_vecinfo:    string
      :returns: nothing
      """
      self.varname = varname
      self.fname_vector = fname_vector

      if (fname_vecinfo == None):
         self.fname_vecinfo = fname_vector # read grid info from vector file itself
      else:
         self.fname_vecinfo = fname_vecinfo # auxiliary file for grid info

      with Dataset(fname_vector,'r') as fid:
         self.time = fid.variables['time'][:]
         self.time_units = fid.variables['time'].units
         self.var_type = fid.variables[varname].dimensions[-1] # 'col' or 'pft' or 'lon'
         self.long_name = fid.variables[varname].long_name

         assert self.var_type in ("column", "pft", "lon"), "variable type <%s> not supported" % self.var_type

         try:
            self.units = fid.variables[varname].units
         except AttributeError:
            self.units = "-"

         if (varname[0:4] == "SNO_"):
            # special case for layered data (like SNO_T, SNO_GS) : use top layer only
            self.data = fid.variables[varname][:,0,:]
         #elif (varname[0:4] == "TSOI"):
         elif (varname.strip() == "TSOI"):
            #print(np.shape(fid.variables[varname][:])) # (1, 25, 97387)
            self.data = fid.variables[varname][:,0,:]
         else:
            self.data = fid.variables[varname][:]

      # WORKAROUND shift data by one month
      #self.data = self.data[[1,2,3,4,5,6,7,8,9,10,11,0]]

      print('INFO: %s: read variable %s, which is of type %s' %(rtnnam(), varname, self.var_type))

      self.ndim = self.data.ndim
      if (self.ndim == 1):
         # static variable
         self.ntime = 1
         self.nvec = len(self.data)
      elif (self.ndim == 2):
         # assume time indexed variable
         self.ntime, self.nvec = self.data.shape
      elif (self.ndim == 3 and self.var_type == "lon"):
         self.ntime, self.nlat, self.nlon = self.data.shape
         self.nvec = self.nlat * self.nlon
      else:
         raise NotImplementedError('Unexpected number of dimensions of input data, ndim = %d > 2' % self.ndim)

      # Read vector indices and grid information
      try:
         self.readVectorInfo()
      except KeyError:
         msg = """
         Required vector information could not be read from file!
         This probably means that the file you supplied does not contain the right variables,
         which may happen for instance when you extracted a variable using ncks or CDO.
         Use optional argument \'fname_vecinfo\' in the constructor to point to a file that contains
         the vector information"""
         raise RuntimeError(msg)

      print('INFO: %s: nlat = %d, nlon = %d' % (rtnnam(), self.nlat, self.nlon))

   def readVectorInfo(self):
      """
      Read vector information (col of pft based) for the variable at hand and store in memory.
      Do the same for general grid information (lat, lon).
      Is called automatically during __init__()
      """
      with Dataset(self.fname_vecinfo,'r') as fid:
         self.lats = fid.variables['lat'][:]
         self.lons = fid.variables['lon'][:]
         if (self.var_type == 'column'):
            self.ixy = fid.variables['cols1d_ixy'][:]
            self.jxy = fid.variables['cols1d_jxy'][:]
            self.lunit   = fid.variables['cols1d_itype_lunit'][:]   # col landunit type (vegetated,urban,lake,wetland,glacier or glacier_mec)
            self.coltype   = fid.variables['cols1d_itype_col'][:]
         elif (self.var_type == 'pft'):
            self.ixy = fid.variables['pfts1d_ixy'][:]
            self.jxy = fid.variables['pfts1d_jxy'][:]
            self.lunit   = fid.variables['pfts1d_itype_lunit'][:]   # col landunit type (vegetated,urban,lake,wetland,glacier or glacier_mec)
            self.coltype   = fid.variables['pfts1d_itype_col'][:]
         elif (self.var_type == 'lon'):
            self.ixy, self.jxy, self.lunit, self.coltype = None, None, None, None
         else:
            raise NotImplementedError('Unknown variable type: '+self.var_type)

      self.nlat = len(self.lats)
      self.nlon = len(self.lons)

   def applyFactor(self, fac, units=None):
      """
      Apply some scalar factor to variable in memory

      :param fac:         factor
      :param units:       new units (if applicable)
      :type fac:          int or float
      :type units:        string
      """
      self.data *= fac
      if (units != None):
         self.units = units
         print("INFO: %s: converted units to: %s" % (rtnnam(), units))

   def setGlcFracCouplerFile(self, filename):
      """
      set glacier fraction from coupler history file

      variables read are named "x2lavg_Sg_ice_covered00" etc.

      :param filename:  filename of coupler history file
      """
      self.mec_frac = np.zeros((GLC_NEC+1,self.nlat,self.nlon)) # One extra for tundra class
      with Dataset(filename,'r') as fid:
         #print(fid.variables)
         for i in range(0,GLC_NEC+1):
            #self.mec_frac[i,:,:] = fid.variables['x2lavg_Sg_ice_covered%02d' % i][:] # CESM 1.99
            self.mec_frac[i,:,:] = fid.variables['x2l_Sg_ice_covered%02d' % i][:] # CESM 2.0

      self.mec_frac = np.ma.masked_greater(self.mec_frac,2) # TODO: ugly, rewrite

   def setGlcFracSurfdat(self, filename):
      """
      set glacier fraction from a surfdat file

      variable read is named "PCT_GLC_MEC_ICESHEET(nglcec, lsmlat, lsmlon)"

      :param filename:  filename of surfdat file
      """
      self.mec_frac = np.zeros((GLC_NEC+1,self.nlat,self.nlon)) # One extra for tundra class
      with Dataset(filename,'r') as fid:
         #print(fid.variables)
         for i in range(1,GLC_NEC+1):
            self.mec_frac[i,:,:] = fid.variables['PCT_GLC_MEC_ICESHEET'][i-1,:,:]

   def setGlcTopoCouplerFile(self, fname_cpl_restart):
      """
      Set MEC topographic height from coupler restart file.
      Height is assumed constant in time (a single copy is stored)

      :param fname_cpl_restart:  filename of coupler restart file
      :type fname_cpl_restart:   string
      """
      self.mec_topo = np.ma.zeros((GLC_NEC+1,self.nlat,self.nlon)) # One extra for tundra class

      with Dataset(fname_cpl_restart,'r') as fid:
         for i in range(0,GLC_NEC+1):
            self.mec_topo[i,:,:] = fid.variables['l2gacc_lx_Sl_topo%02d' % i][:].reshape(self.nlat, self.nlon)# CESM 2.0

      #print(self.mec_topo[:,176,254]) # GrIS
      #print(self.mec_topo[:,164,250]) # GrIS
      #print(self.mec_topo[:,10,10]) # AIS
      #print(self.mec_topo[:,20,76]) # AIS
      #print(self.mec_topo[:,100,100]) # missing values

   def setGlcTopoHistfile(self, fname_vector):
      """
      Set MEC topographic height from TOPO_COL variable in (vector) history file.
      Height is assumed constant in time (a single copy is stored)

      :param fname_vector:    filename of CLM vector file (e.g. XXX.h2.YYY.nc)
      :type fname_vector:     string
      """
      # read TOPO_COL from vector file and convert to gridded
      vmv = VectorMecVariable("TOPO_COL", fname_vector, fname_vecinfo = self.fname_vecinfo)
      tmp = vmv.getGridded3d()[0,:,:,:] # remove time dimension
      self.mec_topo = tmp.transpose(2,0,1) # nlev, nlat, nlon

   def getGridded3d(self):
      """
      Returns vector data as gridded (lat/lon) numpy array with levels (MEC).

      NOTE: This function does NOT take into account variable topographic height
      across MEC columns. It is recommended that for any variable that is
      dynamically downscaled, e.g. temperature, the more elaborate getGridded3dCustomLevels()

      :returns:   numpy array (ntime,nlat,nlon,nlev)
      """
      lon2d,lat2d = np.meshgrid(self.lons,self.lats)
      var_out = np.ma.zeros((self.ntime,self.nlat,self.nlon,GLC_NEC))

      # Mask out all points without GLC_MEC
      var_out[:] = np.ma.masked

      if (self.var_type == "lon"):
         """
         special case: this variable is in fact not unstructured
         """
         print(self.data.shape)
         for lev in range(GLC_NEC):
            var_out[:,:,:,lev] = self.data[:]

      else:
         """
         unstructured variable, type column or pft
         """
         for lev in range(GLC_NEC):
            mask = (self.coltype==(400+lev+1)) # level 0 is tundra, omit this
            idx, = np.where(mask)
            ix = self.ixy[idx]-1
            iy = self.jxy[idx]-1

            #print(tskin[:,idx].shape, var_out[:,iy,ix,lev].shape)
            if (self.ndim == 1):
               var_out[:,iy,ix,lev] = self.data[idx]
            elif (self.ndim == 2):
               var_out[:,iy,ix,lev] = self.data[:,idx]
            else:
               raise NotImplementedError('Unexpected number of dimensions of input data, ndim = %d > 2' % self.ndim)

      # Mask out points with missing value
      var_out = np.ma.masked_greater(var_out, 1e34)

      # report number of non-missing points
      print('INFO: %s: number of non-zero points: %d' %  (rtnnam(), var_out.count() / self.ntime))
      return var_out

   def getGridded2d(self):
      """
      Returns vector data as gridded (lat/lon) numpy array.
      No levels, so weighted by ice_cover percentage.

      :returns:   numpy array (ntime,nlat,nlon)
      """
      var3d = self.getGridded3d() # dimensions (ntime, nlat, nlon, GLC_NEC)
      #print('DEBUG',var3d.shape)

      var_out = np.ma.zeros((self.ntime,self.nlat,self.nlon))

      try:
         frac = np.ma.filled(self.mec_frac, 0.0)
      except AttributeError:
         msg = """
         Glacier fraction has not been set in class VectorMecVariable! You must first set fraction
         using class methods setGlcFracCouplerFile() or setGlcFracSurfdat()"""
         raise AttributeError(msg)

      #print('DEBUG',np.max(frac))

      for lev in range(GLC_NEC):
         #var_out += self.mec_frac[lev+1,:,:] * var3d[:,:,:,lev]
         var_out += frac[lev+1,:,:] * np.ma.filled(var3d[:,:,:,lev], 0.0)

      var_out /= np.sum(frac[1:,:,:], axis=0) # normalize for total fraction ( /= 1.0 when tundra present)

      # Mask out all points without GLC_MEC
      #var_out = np.ma.masked_less(var_out, 1e-4) # TODO: this is quite crude!!

      # Mask out all points without GLC_MEC
      #invalid_mask = np.all(var3d.mask, axis=3)
      #var_out[invalid_mask] = np.ma.masked

      return var_out

   def getGridded3dCustomLevels(self, custom_levs):
      """
      Returns vector data as gridded (lat/lon) numpy array at user specified levels.
      A coupler restart file is required containing the topographic height of each MEC column.

      :param custom_levs:        custom levels
      :type custom_levs:         python list
      :returns:   numpy array (ntime,nlat,nlon,nlev)
      """
      lon2d,lat2d = np.meshgrid(self.lons,self.lats)
      nlev = len(custom_levs)

      var_out = np.ma.zeros((self.ntime,self.nlat,self.nlon,nlev))
      var_out[:] = np.ma.masked # mask out all points without GLC_MEC

      try:
         #topo = np.ma.filled(self.mec_topo, 0.0) # nlev, nlat, nlon
         mec_mask = np.any(self.mec_topo, axis=0)
         print('INFO: %s: using %d grid points out of a total %d' % (rtnnam(), mec_mask.sum(), self.nlat * self.nlon))
      except AttributeError:
         msg = """
         Glacier topography has not been set in class VectorMecVariable! You must first set topography
         using class methods setGlcTopoCouplerFile() or setGlcFracHistfile()"""
         raise AttributeError(msg)

      # get 3D view of variable
      var3d = self.getGridded3d() # dimensions (ntime, nlat, nlon, GLC_NEC)

      mec_topo2 = self.mec_topo

      # mask out all tundra columns
      mec_topo2[0,:,:] = np.ma.masked

      # mask out missing MEC columns (they have 0 height)
      mec_topo2 = np.ma.masked_less(mec_topo2, 1e-3)

      nvalid = np.ma.any(mec_topo2, axis=0).sum()
      print('INFO: %s: removing %d invalid grid points: they only contained tundra class (%d remaining)' % (rtnnam(), mec_mask.sum() - nvalid, nvalid))
      mec_mask = np.any(mec_topo2, axis=0)

      # loop grid points that contain at least 1 MEC column
      ix, iy = np.where(mec_mask)
      #print(ix.shape, iy.shape)
      for ii in range(len(ix)):
      #for ii in [10, 2000, 6000, 6500]: # testing with 2 AIS points and 2 GrIS points
         xx = mec_topo2[:,ix[ii], iy[ii]]
         exist, = np.ma.where(xx)
         kexist = len(exist)
         xp = xx[exist] # elevations of all MEC classes that exist

         # loop time steps
         for itime in range(self.ntime):
            # function values used in interpolation (= field values corresponding to heights xp)
            # indices are shifted by one due to presence of tundra class in mec_topo
            fp = var3d[itime, ix[ii], iy[ii], exist-1]

            if (kexist > 1):
               # Interpolate to target levels using first order (= linear) splines from Scipy.
               # This way, we can linearly interpolate / extrapolate to any level, even sea level (z = 0).
               # Note: whether linear extrapolation makes sense really depends on the variable at hand.
               # Alternatively, numpy.interp() may be used, which extrapolates with constant value
               # (i.e. repeating the boundary value).
               spl = InterpolatedUnivariateSpline(np.asarray(xp), np.asarray(fp), k=1)
               var_out[itime, ix[ii], iy[ii], :] = spl(custom_levs)

            else:
               # single MEC column at this grid point, can only be constantly extrapolated
               var_out[itime, ix[ii], iy[ii], :] = fp

      # Mask out points with missing value
      #var_out = np.ma.masked_greater(var_out, 1e34)

      # report number of non-missing points
      print('INFO: %s: number of non-zero points: %d' %  (rtnnam(), var_out.count() / self.ntime))
      return var_out

   def divideByGriddedField(self,gfield):
      """
      To calculate albedo, we need to divide FSR by a regular lat/lon field.
      This routine implements the division by a gridded field.

      :param gfield:  numpy array
      """
      print(self.nlat, self.nlon)
      print(gfield.shape)
      if(self.nlat != gfield.shape[0] or self.nlon != gfield.shape[1]):
         raise ValueError('grid dimensions do not match!')

      #print(self.ixy[0:10]-1)
      #print(self.jxy[0:10]-1)

      #coords = list(zip(self.ixy-1 ,self.jxy-1))
      #print(coords)
      print(gfield[0,0])
      print(gfield[100,100])

      #print(gfield[self.jxy-1,self.ixy-1][1000:1010])
      fsds = gfield[self.jxy-1,self.ixy-1]
      print(self.data.shape)
      print(fsds.shape)
      self.data /= fsds
         #print(tskin[:,idx].shape, var_out[:,iy,ix,lev].shape)
         #var_out[:,iy,ix,lev] = self.data[:,idx]


#-*- coding: UTF-8 -*-
from pymatgen import MPRester
from pymatgen.analysis.phase_diagram import PhaseDiagram, PDPlotter
import collections
import sys, os
import time
import string
import re
from functools import reduce
os.system('rm -rf structure; mkdir structure')

ChemicalSymbols = [ 'H',  'He', 'Li', 'Be','B',  'C',  'N',  'O',  'F',
                    'Ne', 'Na', 'Mg', 'Al', 'Si','P',  'S',  'Cl', 'Ar', 'K',
                    'Ca', 'Sc', 'Ti', 'V',  'Cr','Mn', 'Fe', 'Co', 'Ni', 'Cu',
                    'Zn', 'Ga', 'Ge', 'As', 'Se','Br', 'Kr', 'Rb', 'Sr', 'Y',
                    'Zr', 'Nb', 'Mo', 'Tc', 'Ru','Rh', 'Pd', 'Ag', 'Cd', 'In',
                    'Sn', 'Sb', 'Te', 'I',  'Xe','Cs', 'Ba', 'La', 'Ce', 'Pr',
                    'Nd', 'Pm', 'Sm', 'Eu', 'Gd','Tb', 'Dy', 'Ho', 'Er', 'Tm',
                    'Yb', 'Lu', 'Hf', 'Ta', 'W','Re', 'Os', 'Ir', 'Pt', 'Au',
                    'Hg', 'Tl', 'Pb', 'Bi', 'Po','At', 'Rn', 'Fr', 'Ra', 'Ac',
                    'Th', 'Pa', 'U',  'Np', 'Pu','Am', 'Cm', 'Bk', 'Cf', 'Es',
                    'Fm', 'Md', 'No', 'Lr']
M = ['Sc', 'Ti', 'V', 'Cr', 'Mn',
     'Zr', 'Nb', 'Mo',
     'Hf', 'Ta', 'W']
A = ['Al', 'Si', 'P', 'S',
     'Ga', 'Ge', 'As',
     'Cd', 'In', 'Sn',
     'Ti', 'Pb']
X = ['H',
     'B', 'C', 'N', 'O', 'F',
     'Si', 'P', 'S', 'Cl',
     'Br']

def get_formula(x, y, z):
    formula = []
    for ix in x:
        for iy in y:
            for iz in z:
                formula.append([ix, iy, iz])
    return formula

#comps = get_formula(M, A, X)
comps=[['Mg', 'Al', 'O']]
f=open('in.txt','w')
fcsv=open('id_prop.csv','w')
mpr = MPRester("7UnVyVXyetJ5WK3r")
f1=open('name.txt','w')

def numbersss(numbers):
    if len(numbers) == 1:
        return 1

#    elif len(numbers) == 2 :
#        a = int(numbers[0])
#        b = int(numbers[1])
#        if a>b:
#            t = a
#            a = b
#            b = t
#        for n  in range(1,1+(a)):
#            if (a)%n==0 and (b)%n==0:
#                i = [n]
#                i.sort()
#        return i[-1]
#
    else:            #len(numbers) > 2:
        mx = int(reduce(lambda x ,y: max(x,y),numbers))
        while True:
            flag =True
            for number in numbers :
                number=int(number)
                if  (number) % int(mx) !=0 :
                    flag = False
                    break
            if flag ==True:
                return mx

            mx -= 1

for comp in comps:
    print(comp)
    try:
        entries = mpr.get_entries_in_chemsys(comp)
    except:
        continue
    if len(entries) == 0:
        continue
#    pd = PhaseDiagram(entries)
#    data = collections.defaultdict(list)
    for e in entries:
        #print(e)
        #print(type(e))
        #print(e.energy)
#        print(e.entry_id, e.composition, e.energy)
  #      continue
#        ehull = pd.get_e_above_hull(e)
#        if ehull < 0.00000001 :
        com = e.entry_id
        start_time = time.time()
        prop = mpr.query(criteria={"task_id": com}, properties=[\
        "formation_energy_per_atom", \
        "energy_per_atom", \
        "spacegroup", \
        "pretty_formula", \
        "cif"])
        end_time = time.time()
        run_time = end_time - start_time
        #print('run_time', run_time)

        form_energy =  prop[0]['formation_energy_per_atom']
        energy =  prop[0]['energy_per_atom']
        sp =  prop[0]['spacegroup']['symbol']
        name =  prop[0]['pretty_formula']
#        print(name)
        f1.write(name+'\n')
        struct = prop[0]['cif']

        line=(struct.split('\n')[11])
        #line=(ccc[11])
        if '_chemical_formula_sum' in line :
            a=line.split('   ')[1].strip()
            #a=a[1]
            #a=a.strip()
            if a[0] == "'" :
                a=eval(a)
            b=a.split(' ')
            print(b) # 每个元素及数量
            print('-------')
            element1=[]
            numbers=[]
            for i1 in b:
                s=i1.rstrip(string.digits)
                num= (re.findall(r"\d+\.?\d*",i1))
                element1.append(s)
                num=" ".join(num)
                numbers.append(num)
            n=numbersss(numbers)
            print(numbers)
            print(n)
            reduce_for=[]
            total=0
            dic={}
            for i in range(len(b)):
                dic[element1[i]]=(int(int(numbers[i])/int(n)))
                reduce_for.append(element1[i])
                red=(int(int(numbers[i])/int(n)))
                red1=str(red)
                total+= red
                reduce_for.append(red1)
            print(reduce_for)
            reduce_for1=(" ".join(reduce_for))
            print(reduce_for1)
            reduce_for1=reduce_for1.replace(' ','')                  #(" ".join(reduce_for1.split(' ')))
            print(reduce_for1,n,dic,total)
            print('####################')

        fcif = open('./structure/' + com + '_' + name + '.cif','w')
        fcif.write(struct)
        fcif.close()
       # a=open('./structure/' + com + '_' + name + '.cif','r')
       # print(a)
       # while True:
       #     line=a.readline()
       #     if '_chemical_formula_sum' in line :
       #         a=(line.split()[1:])
       #         b=[str(i) for i in a]
       #         c=''.join(b)
       #         f1.write(c+'\n')
        f.write('%s  ' % name)
        f.write('%15.10f       %s\n' % (energy, com))
        fcsv.write(com + '_' + name + ',  ' + str(energy) + '  ' + str(form_energy) + '  ' + str(sp) + '\n')
f.close()
fcsv.close()
f1.close()


#!/usr/bin/env python3
import rospy
from std_msgs.msg import String
import cv2 as cv
import time
import numpy as np

rospy.init_node("LaneDetection_node")
rospy.loginfo ("Lane detectio node has been Started !")
pub = rospy.Publisher('command', String, queue_size=10)

def canny(image):
    gray_img = cv.cvtColor(image, cv.COLOR_RGB2GRAY)
    blur_img = cv.GaussianBlur(gray_img, (1, 1), 0)
    canny_img = cv.Canny(blur_img, 50, 200, None, 3)
    return canny_img
def line_detection (masked_img):
    lines_list = []
    lines = cv.HoughLinesP(
        masked_img,  # Input edge image
        1,  # Distance resolution in pixels
        np.pi / 180,  # Angle resolution in radians
        threshold=50,  # Min number of votes for valid line
        minLineLength=30,  # Min allowed length of line
        maxLineGap=1  # Max allowed gap between line for joining them
    )
    return lines
def region_of_interest(image):
    height = 288
    width = 512
    polygons = np.array([[(0, 288), (512, 288), (256,180)]])
    #polygons2 = np.array([[(0, 350), (0, height), (width, 350), (width, height)]])
    mask = np.zeros_like(image)
    #cv.rectangle( mask, (0, 350), (width, height), 255, -1)
    cv.fillPoly(mask, polygons, 255)
    #cv.fillPoly(mask, polygons2, 255)
    masked_image = cv.bitwise_and(image, mask)

    return masked_image
def error(image, offset):
    # x - y37/105 - 193 = 0
    e = 0
    t = 0
    #print(image[287][511])
    for x in range(0,511):
        for y in range (180, 287):
            if image[y][x] ==  255 :
                t = t + 1
                if x > 256 :
                    e = e + 1
                else:
                    e = e - 1

    if t ==  0:
        return 0

    return e/t + offset

def decode(V ,off):
    v2 = V + off*V
    v1 = V - off*V

    return v1, v2

def discrete_pid(Kp, Ki, Kd, dt, max_output, min_output):
    # Khởi tạo các biến
    error_sum = 0
    last_error = 0

    def pid(error):
        nonlocal error_sum, last_error

        # Tính toán tổng lỗi tích phân
        error_sum += error * dt

        # Tính toán đạo hàm của lỗi
        d_error = (error - last_error) / dt
        last_error = error

        # Tính toán giá trị điều khiển mới
        output = Kp * error + Ki * error_sum + Kd * d_error

        # Giới hạn giá trị điều khiển trong khoảng cho phép
        if output > max_output:
            output = max_output
        elif output < min_output:
            output = min_output

        return output

    return pid

# Khởi tạo hàm PID
pid = discrete_pid(Kp=0.02, Ki=0.004, Kd= 0, dt=0.33, max_output= 1 , min_output= -1)

#cap = cv.VideoCapture("/home/pi/Desktop/backup_9_1/video_record6.h264")
cap = cv.VideoCapture(0)
cap.set(3,512)
cap.set(4,288)
count = 0
while cap.isOpened() and not rospy.is_shutdown():
    ret, frame = cap.read()
    #start = time.time()
    if ret == False:
        break

    img = frame
    gray_img = cv.cvtColor(img, cv.COLOR_RGB2GRAY)
    gray_img = region_of_interest(gray_img)
    gray_img = cv.equalizeHist(gray_img)
    mask_gray = cv.inRange(gray_img, 230, 255)
    kernel = np.ones((8, 5), np.uint8)
    mask_gray = cv.erode(mask_gray, kernel)
    mask_gray = cv.dilate(mask_gray, kernel)

        # lines = line_detection(mask_gray)
        # if lines is not None:
        #     for points in lines:
        #         # Extracted points nested in the list
        #         x1, y1, x2, y2 = points[0]
        #         cv.line(img, (x1, y1), (x2, y2), (0, 255, 0), 2)
    cv.line(img, (256, 288), (256, 200),(0, 255, 0), 2 )
    cv.imshow("line", img)
    cv.imshow("mask", mask_gray)
        #cv.imshow("gray", gray_img)

    e = error(mask_gray, 0)
    rospy.loginfo(e)
    control = pid(e)
    V1, V2 = decode(60, control)
    #print(V1, "\n", V2)

    command = "!V"+ str(int(V1)) + ";"+str(int(V2)) + "#"
    #print(command)
    pub.publish(command)

    #end = time.time()
    #second = end - start
    #print("fps =",1/second)
    if cv.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv.destroyAllWindows()


import taichi as ti
import numpy as np
import argparse
from ray_tracing_models import Ray, Camera, Hittable_list, Sphere, PI, random_in_unit_sphere, refract, reflect, reflectance, random_unit_vector
ti.init(arch=ti.gpu)

# Canvas
aspect_ratio = 1.0
image_width = 800
image_height = int(image_width / aspect_ratio)
canvas = ti.Vector.field(3, dtype=ti.f32, shape=(image_width, image_height))

# Rendering parameters
samples_per_pixel = 4
max_depth = 10
sample_on_unit_sphere_surface = True

@ti.kernel
def render():
    for i, j in canvas:
        u = (i + ti.random()) / image_width
        v = (j + ti.random()) / image_height
        color = ti.Vector([0.0, 0.0, 0.0])
        for n in range(samples_per_pixel):
            ray = camera.get_ray(u, v)
            color += ray_color(ray)
        color /= samples_per_pixel
        canvas[i, j] += color

# Path tracing
@ti.func
def ray_color(ray):
    color_buffer = ti.Vector([0.0, 0.0, 0.0])
    brightness = ti.Vector([1.0, 1.0, 1.0])
    scattered_origin = ray.origin
    scattered_direction = ray.direction
    p_RR = 0.8
    for n in range(max_depth):
        if ti.random() > p_RR:
            break
        is_hit, hit_point, hit_point_normal, front_face, material, color = scene.hit(Ray(scattered_origin, scattered_direction))
        if is_hit:
            if material == 0:
                color_buffer = color * brightness
                break
            else:
                # Diffuse
                if material == 1:
                    target = hit_point + hit_point_normal
                    if sample_on_unit_sphere_surface:
                        target += random_unit_vector()
                    else:
                        target += random_in_unit_sphere()
                    scattered_direction = target - hit_point
                    scattered_origin = hit_point
                    brightness *= color
                # Metal and Fuzz Metal
                elif material == 2 or material == 4:
                    fuzz = 0.0
                    if material == 4:
                        fuzz = 0.4
                    scattered_direction = reflect(scattered_direction.normalized(),
                                                  hit_point_normal)
                    if sample_on_unit_sphere_surface:
                        scattered_direction += fuzz * random_unit_vector()
                    else:
                        scattered_direction += fuzz * random_in_unit_sphere()
                    scattered_origin = hit_point
                    if scattered_direction.dot(hit_point_normal) < 0:
                        break
                    else:
                        brightness *= color
                # Dielectric
                elif material == 3:
                    refraction_ratio = 1.5
                    if front_face:
                        refraction_ratio = 1 / refraction_ratio
                    cos_theta = ti.min(-scattered_direction.normalized().dot(hit_point_normal), 1.0)
                    sin_theta = ti.sqrt(1 - cos_theta * cos_theta)
                    # total internal reflection
                    if refraction_ratio * sin_theta > 1.0 or reflectance(cos_theta, refraction_ratio) > ti.random():
                        scattered_direction = reflect(scattered_direction.normalized(), hit_point_normal)
                    else:
                        scattered_direction = refract(scattered_direction.normalized(), hit_point_normal, refraction_ratio)
                    scattered_origin = hit_point
                    brightness *= color
                brightness /= p_RR
    return color_buffer

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='Naive Ray Tracing')
    parser.add_argument(
        '--max_depth', type=int, default=10, help='max depth (default: 10)')
    parser.add_argument(
        '--samples_per_pixel', type=int, default=4, help='samples_per_pixel  (default: 4)')
    parser.add_argument(
        '--samples_in_unit_sphere', action='store_true', help='whether sample in a unit sphere')
    args = parser.parse_args()

    max_depth = args.max_depth
    samples_per_pixel = args.samples_per_pixel
    sample_on_unit_sphere_surface = not args.samples_in_unit_sphere
    scene = Hittable_list()

    # Light source
    scene.add(Sphere(center=ti.Vector([0, 5.4, -1]), radius=3.0, material=0, color=ti.Vector([10.0, 10.0, 10.0])))
    # Ground
    scene.add(Sphere(center=ti.Vector([0, -100.5, -1]), radius=100.0, material=1, color=ti.Vector([0.8, 0.8, 0.8])))
    # ceiling
    scene.add(Sphere(center=ti.Vector([0, 102.5, -1]), radius=100.0, material=1, color=ti.Vector([0.8, 0.8, 0.8])))
    # back wall
    scene.add(Sphere(center=ti.Vector([0, 1, 101]), radius=100.0, material=1, color=ti.Vector([0.8, 0.8, 0.8])))
    # right wall
    scene.add(Sphere(center=ti.Vector([-101.5, 0, -1]), radius=100.0, material=1, color=ti.Vector([0.6, 0.0, 0.0])))
    # left wall
    scene.add(Sphere(center=ti.Vector([101.5, 0, -1]), radius=100.0, material=1, color=ti.Vector([0.0, 0.6, 0.0])))

    # Diffuse ball
    scene.add(Sphere(center=ti.Vector([0, -0.2, -1.5]), radius=0.3, material=1, color=ti.Vector([0.8, 0.3, 0.3])))
    # Metal ball
    scene.add(Sphere(center=ti.Vector([-0.8, 0.2, -1]), radius=0.7, material=2, color=ti.Vector([0.6, 0.8, 0.8])))
    # Glass ball
    scene.add(Sphere(center=ti.Vector([0.7, 0, -0.5]), radius=0.5, material=3, color=ti.Vector([1.0, 1.0, 1.0])))
    # Metal ball-2
    scene.add(Sphere(center=ti.Vector([0.6, -0.3, -2.0]), radius=0.2, material=4, color=ti.Vector([0.8, 0.6, 0.2])))

    camera = Camera()
    gui = ti.GUI("Ray Tracing", res=(image_width, image_height))
    canvas.fill(0)
    cnt = 0
    while gui.running:
        render()
        cnt += 1
        gui.set_image(np.sqrt(canvas.to_numpy() / cnt))
        gui.show()