from udp import UDPsocket
from socket import *
import struct
import time
import threading

# 计算检验和
def calc_checksum(payload):
    sum = 0
    for byte in payload:
        sum += byte
    sum = -(sum % 256)
    return sum & 0xFF

# 验证信息的正确性
def calc_sum(payload):
    sum = 0
    for byte in payload:
        sum += byte
    return sum

# 将数字转为四字节的16进制字符串
def tran_into_hex_str_four(message):
    num = str(hex(message))
    le = 10 - len(num)
    re = '0' * le + num[2:]
    return re

# 打包十六进制字符串，转化为udp的16进制报文
def trans_into_hex(message):
    re = b''
    key = 0
    while key < len(message):
        intg = int(message[key:key+2], 16)
        re = re + struct.pack('B', intg)
        key = key + 2
    return re

# 数字转换
def ascii_to_num(message):
    key = 0
    re = 0
    num = len(message)
    while key < num:
        re = re + 256**(num-1-key)*int(message[key])
        key = key + 1
    return re

# 将文本转换为十六进制udp内容
def tran_into_hex_str(message, length):
    num = str(hex(message))
    le = length + 2 - len(num)
    re = '0' * le + num[2:]
    return re

# 数字转换
def two_byte_ascii_to_num(message):
    key = 0
    re = 0
    while key < len(message):
        re = re + 16**(3-key)*int(message[key:key+1])
        key = key + 1
    return re

# 将字符串payload转化为比特码payload
def payload_code_tran(message):
    key = 0
    re = b''
    while key < len(message):
        re = re + struct.pack('B', ord(message[key:key + 1]))
        key = key + 1
    return re

# 将信息封装为一个payload
class payload():
    def __init__(self, syn=0, fin=0, ack=0, seq=0, seq_ack=0, payload=''):
        self.syn = syn
        self.fin = fin
        self.ack = ack
        self.seq = seq
        self.seq_ack = seq_ack
        self.payload = payload
        self.len = len(payload)
        self.payload_code = payload_code_tran(payload)
        self.payload_list = [syn, fin, ack]
        self.decimal_to_one_byte_hex(self.seq, 8)
        self.decimal_to_one_byte_hex(self.seq_ack, 8)
        self.decimal_to_one_byte_hex(self.len, 8)
        key = 0
        while key < len(payload):
            self.payload_list.append(ord(payload[key]))
            key = key + 1
        self.checksum = calc_checksum(self.payload_list)

    # 字符串化
    def to_ascii(self):
        re = struct.pack('B', self.syn) + struct.pack('B', self.fin) + struct.pack('B', self.ack) + trans_into_hex(tran_into_hex_str(self.seq, 8))
        re = re + trans_into_hex(tran_into_hex_str(self.seq_ack, 8))
        re = re + trans_into_hex(tran_into_hex_str(self.len, 8))
        re = re + trans_into_hex(tran_into_hex_str(self.checksum, 4))
        re = re + self.payload_code
        return re

    # 十进制数字转化为特定长度的十六进制
    def decimal_to_one_byte_hex(self, num, len):
        str = hex(num)
        length = str.__len__()
        str = '0' * (len - (length - 2)) + str[2:]
        key = 0
        while key < str.__len__():
            self.payload_list.append(int(str[key:key + 2], 16))
            key += 2

# 解析收到的payload
class payload_resolver():
    def __init__(self, message):
        self.checksum_list = []
        self.syn = message[0]
        self.fin = message[1]
        self.ack = message[2]
        self.seq = ascii_to_num(message[3:7])
        self.seq_ack = ascii_to_num(message[7:11])
        self.len = ascii_to_num(message[11:15])
        self.checksum = ascii_to_num(message[15:17])
        self.payload = message[17:]
        if calc_checksum(message) == 0:
            self.jug = True
        else:
            self.jug = False

    # 字符串化
    def to_string(self):
        re = ''
        re = re + 'SYN: ' + str(self.syn) + '\n'
        re = re + 'FIN: ' + str(self.fin) + '\n'
        re = re + 'ACK: ' + str(self.ack) + '\n'
        re = re + 'SEQ: ' + str(self.seq) + '\n'
        re = re + 'SEQ_ACK: ' + str(self.seq_ack) + '\n'
        re = re + 'LEN: ' + str(self.len) + '\n'
        re = re + 'CHECKSUM: ' + str(self.checksum) + '\n'
        re = re + 'PAYLOAD: ' + str(self.payload) + '\n'
        re = re + 'JUG: ' + str(self.jug) + '\n'
        return re

# rdt传输udp
class socket_(UDPsocket):
    def __init__(self, window_size=3, base=1, next_sequence=1, recent_seq=0, retrans_time=2, syn=0):
        super().__init__()
        self.send_socket = socket(AF_INET, SOCK_DGRAM)
        self.seq = {}
        self.seq_ack = {}
        self.message = None
        self.window_size = window_size
        self.datas = []
        self.base = base
        self.next_sequence = next_sequence
        self.recent_seq = recent_seq
        self.send_process = threading.Thread(target=self.send_process)
        self.recv_process = threading.Thread(target=self.recv_process)
        self.send_process_jug = False
        self.recv_process_jug = False
        self.syn = syn

    # 接受完整的信息
    def recvfrom(self):
        while True:
            UDPsocket.setblocking(self, 5)
            result = UDPsocket.recvfrom(self, bufsize=4096)
            if result is None:
                continue
            else:
                try:
                    data = payload_resolver(result[0])
                except:
                    continue
                address = result[1]
                if data.jug:
                    if address not in self.seq.keys():
                        self.seq[address] = 0
                        self.seq_ack[address] = 0
                    if data.seq is not self.seq_ack[address]:
                        re = payload(seq_ack=self.seq_ack[address])
                        UDPsocket.sendto(self, re.to_ascii(), address)
                        continue
                    else:
                        self.seq_ack[address] = data.seq + data.len
                        re = payload(seq_ack=self.seq_ack[address])
                        UDPsocket.sendto(self, re.to_ascii(), address)
                        break
                else:
                    if address not in self.seq.keys():
                        self.seq[address] = 0
                        self.seq_ack[address] = 0
                    re = payload(seq_ack=self.seq_ack[address])
                    UDPsocket.sendto(self, re.to_ascii(), address)
                    continue
        # print('Inside function: '+str(data.payload))
        return data.payload, address

    def recv(self):
        return self.recvfrom()[0]

    def send(self, data):
        message = data[0]
        address = data[1]
        bytes = message.to_ascii()
        UDPsocket.sendto(self, bytes, address)

    # 发送信息
    def sendto(self, data, address):
        if address not in self.seq.keys():
            self.seq[address] = 0
            self.seq_ack[address] = 0
        load = payload(payload=data, seq=self.recent_seq, seq_ack=self.seq_ack[address])
        self.datas.append([load, address])
        self.recent_seq += load.len

    # 发送进程，用于发送流水线中的报文
    def send_process(self):
        while True:
            if self.datas.__len__() > 0:
                max = self.window_size if self.datas.__len__() > self.window_size else self.datas.__len__()
                for key in range(0, max):
                    data = self.datas[key]
                    self.send(data)
            time.sleep(0.5)

    # 启动进程
    def start_send_process(self):
        self.send_process.setDaemon(True)
        self.send_process.start()

    def start_recv_process(self):
        self.recv_process.setDaemon(True)
        self.recv_process.start()

    # 接受进程，用于接受发送出去的报文返回的ack，确认流水线中的报文正确的到达
    def recv_process(self):
        while True:
            UDPsocket.setblocking(self, 0)
            try:
                reply = UDPsocket.recvfrom(self, bufsize=4096)
                if reply is not None:
                    result = payload_resolver(reply[0])
                    if not result.jug:
                        continue
                else:
                    continue
            except:
                time.sleep(0.5)
                continue
            try:
                pack = self.datas[0]
                address = reply[1]
            except IndexError:
                continue
            if address not in self.seq.keys():
                self.seq[address] = 0
                self.seq_ack[address] = 0
            if result.seq_ack is self.seq[address] + pack[0].len:
                print('\"' + str(pack[0].payload) + '\"' + 'send successfully!')
                self.seq[address] += pack[0].len
                self.datas.remove(pack)
                continue
            elif result.seq is self.seq_ack[address] and result.syn is 1:
                print('\"' + str(result.payload) + '\"' + 'receive successfully!')
                self.seq_ack[address] += result.len
                continue
            else:
                time.sleep(0.5)
                continue


# https://codeforces.com/contest/1772/problem/D

# thoughts on cases:
# 3 valid cases:

# 1. always non-decreasing:
# return first element (smallest number)

# 2. always non-increasing:
# return last element (largest number)

# 3. always non-decreasing with one contiguous non-increasing segment
# to check if valid, take the maximum length non-increasing segment
# return
import sys

input = lambda: sys.stdin.readline().strip()
read = lambda: map(int, input().split())

t, = read()
for _ in range(t):
    n, = read()
    a = list(read())


from tkinter import *
import tkinter as tk
root= Tk()

idli = tk.IntVar()
dosa = tk.IntVar()
puri = tk.IntVar()
vada = tk.IntVar()
palav = tk.IntVar()
biryani = tk.IntVar()

def var_states():
   a=idli.get()
   b=dosa.get()
   c=puri.get()
   d=vada.get()
   e=palav.get()
   f=biryani.get()
   root1 = Tk()
   root1.geometry("200x200")
   cost = a*15 + b*50 + c*35 + d*15 + e*45 + f*120
   tk.Label(root1, text="the cost was : "+str(cost),justify = tk.CENTER,padx = 20).pack()

root.geometry("500x500")
tk.Label(root, text="Simple Restaurant menu :",justify = tk.CENTER,padx = 20).pack()
tk.Label(root, text="",justify = tk.CENTER,padx = 20).pack()

tk.Label(root, text="ITEM",justify = tk.CENTER,padx = 40).place(relx = 0.1, rely = 0.1)
tk.Label(root, text="PRICE",justify = tk.CENTER,padx = 40).place(relx = 0.5, rely = 0.1)

tk.Checkbutton(root, text="IDLI",padx = 20, variable=idli).place(relx = 0.1, rely = 0.17)
tk.Label(root, text="Rs. 15",justify = tk.CENTER,padx = 40).place(relx = 0.5, rely = 0.17)

tk.Checkbutton(root,text="MASALA DOSA", padx = 20,  variable=dosa).place(relx = 0.1, rely = 0.23)
tk.Label(root, text="Rs. 50",justify = tk.CENTER,padx = 40).place(relx = 0.5, rely = 0.23)

tk.Checkbutton(root,text="PURI", padx = 20,  variable=puri).place(relx = 0.1, rely = 0.29)
tk.Label(root, text="Rs. 35",justify = tk.CENTER,padx = 40).place(relx = 0.5, rely = 0.29)

tk.Checkbutton(root,text="VADA", padx = 20,  variable=vada).place(relx = 0.1, rely = 0.35)
tk.Label(root, text="Rs. 15",justify = tk.CENTER,padx = 40).place(relx = 0.5, rely = 0.35)

tk.Checkbutton(root,text="PALAV", padx = 20,  variable=palav).place(relx = 0.1, rely = 0.41)
tk.Label(root, text="Rs. 45",justify = tk.CENTER,padx = 40).place(relx = 0.5, rely = 0.41)

tk.Checkbutton(root,text="BIRYANI", padx = 20,  variable=biryani).place(relx = 0.1, rely = 0.47)
tk.Label(root, text="Rs. 120",justify = tk.CENTER,padx = 40).place(relx = 0.5, rely = 0.47)

Button(root, text='Quit', command=root.quit).place(relx = 0.20, rely = 0.60)
Button(root, text='Submit', command=var_states).place(relx = 0.40, rely = 0.60)

root.mainloop()


from hdlConvertorAst.hdlAst import HdlOp, HdlStmIf, HdlStmBlock, HdlStmAssign, HdlStmCaseType
from hdlConvertorAst.to.hdlUtils import Indent, iter_with_last
from hdlConvertorAst.to.hwt.expr import ToHwtExpr

class ToHwtStm(ToHwtExpr):

    def visit_HdlStmProcess(self, o):
        """
        :type o: HdlStmProcess
        """
        w = self.out.write
        if o.labels:
            w("# ")
            if o.labels:
                w(o.labels[0])
                # w(", ")
        #w("sens: ")
        #if o.sensitivity:
        #    for last, s in iter_with_last(o.sensitivity):
        #        if isinstance(s, HdlOp):
        #            w(str(s.fn))
        #            w(" ")
        #            self.visit_iHdlExpr(s.ops[0])
        #        else:
        #            self.visit_iHdlExpr(s)
        #        if not last:
        #            w(", ")
        w("\n")
        self.visit_doc(o)
        self.visit_iHdlStatement(o.body)
        #w("\n")

    def visit_HdlStmBlock(self, o):
        """
        :type o: HdlStmBlock
        """
        self.visit_doc(o)
        w = self.out.write
        for is_last, i in iter_with_last(o.body):
            self.visit_iHdlStatement(i)
            if not is_last:
                w(",\n")

    def visit_HdlStmIf(self, o):
        """
        :type stm: HdlStmIf

        if cond:
            ...
        else:
            ...

        will become

        c, cVld = sim_eval_cond(cond)
        if not cVld:
            # ivalidate outputs
        elif c:
            ...
        else:
            ...
        """
        self.visit_doc(o)
        w = self.out.write
        w("If(")
        self.visit_iHdlExpr(o.cond)
        w(",\n")
        with Indent(self.out):
            self.visit_iHdlStatement(o.if_true)
            w("\n")
        w(")")
        for (c, _stm) in o.elifs:
            w(".Elif(")
            self.visit_iHdlExpr(c)
            w(",\n")
            with Indent(self.out):
                self.visit_iHdlStatement(_stm)
            w("\n")
            w(")")

        ifFalse = o.if_false
        if ifFalse is not None:
            w(".Else(\n")
            with Indent(self.out):
                self.visit_iHdlStatement(ifFalse)
                w("\n")
            w(")")

    def visit_HdlStmAssign(self, o):
        """
        :type o: HdlStmAssign
        """
        self.visit_doc(o)
        w = self.out.write
        self.visit_iHdlExpr(o.dst)
        if o.is_blocking:
            raise NotImplementedError(o)
        if o.time_delay is not None:
            raise NotImplementedError()
        if o.event_delay is not None:
            raise NotImplementedError()
        w("(")
        self.visit_iHdlExpr(o.src)
        w(")")

    def visit_HdlStmCase(self, o):
        """
        :type o: HdlStmCase
        """
        self.visit_doc(o)
        w = self.out.write
        if o.type != HdlStmCaseType.CASE:
            raise NotImplementedError()
        w("Switch(")
        self.visit_iHdlExpr(o.switch_on)
        w(")")
        with Indent(self.out):
            for c, stm in o.cases:
                w("\\\n")
                w(".Case(")
                self.visit_iHdlExpr(c)
                w(",\n")
                with Indent(self.out):
                    self.visit_iHdlStatement(stm)
                w(")")
            if o.default is not None:
                w("\\\n")
                w(".Default(\n")
                with Indent(self.out):
                    self.visit_iHdlStatement(o.default)
                    w(")")


from lmfit import Parameters, Parameter
from liboptpy.lib.mechanism_model import mechanism
from liboptpy.policy import Policy, PolicyLab, PolicyDDPG
from liboptpy.gradients.bayesian_network import bayesian_network, bayesian_network_posterior
from liboptpy.gradients.BN_gradient import BN_gradient
import liboptpy.constr_solvers._proj_gd as cs
import liboptpy.step_size as ss
import numpy as np
import pandas as pd
from liboptpy.simulator.bio_env import fermentation_env, fermentation_env2, two_unit_operations_env
from liboptpy.lib.compute_reward import compute_avg_reward
from liboptpy.simulator.bioprocess_2stages import fermentation_purification_simulator

feed_profile = [0.000000e+00, 3.131863e-05, 8.332656e-04, 3.641692e-03, 9.661720e-03, 7.794840e-03, 7.359495e-03,
                5.043735e-03, 5.347406e-03, 2.486022e-03, 2.385212e-03, 2.301092e-03, 1.724154e-03, 6.246971e-04,np.log(40), np.log(60)]
# feed_profile = [0.0000000000, 0.0003131863, 0.0041663280, 0.0098325692, 0.0087833820, 0.0077948404, 0.0056611497,
#                 0.0038797959, 0.0041133889, 0.0019123248, 0.0015901411, 0.0015340615, 0.0011494357, 0.0004164647]

#
# feed_profile = [0.0000000000,
#                 0.0003624999,
#                 0.0048485723,
#                 0.0119220315,
#                 0.0102440275,
#                 0.0086513113,
#                 0.0061447560,
#                 0.0039552809,
#                 0.0048855832,
#                 0.0020873176,
#                 0.0017531639,
#                 0.0015830354,
#                 0.0010269718,
#                 0.0004710181,
#                 np.log(40), np.log(60)]
# feed_profile = [0.0000000000, 0.0003131863, 0.0041663280, 0.0098325692, 0.0087833820, 0.0077948404, 0.0056611497,
#                 0.0038797959, 0.0041133889, 0.0019123248, 0.0015901411, 0.0015340615, 0.0011494357, 0.0004164647,
#                 np.log(40), np.log(60)]

# feed_profile = [0, 4.440000e-19, 0.002515011, 0.004502748, 0.005973753, 0.007297723, 0.006291863,  5.175258e-03, 0.0025989402, 0.001611073, 0.001167248, 0.001135921, 0.0010017584, 0.0001669597]
# feed_profile = [0.000000e+00,  0.000000e+00,  2.801345e-03,  6.500561e-03,  7.838870e-03,  7.379049e-03,  5.426248e-03, -4.236836e-05,
#  1.575191e-02,  1.355505e-03, -1.190099e-03,  1.957627e-03,  9.760737e-04,  1.626789e-04]
real_measurement = [0.0, 5.0, 10.0, 23.0, 28.0, 34.0, 47.5, 55.0, 72.0, 80.0, 95.0, 102.0, 120.0, 140.0]

if __name__ == '__main__':

    params = Parameters()

    params.add('alpha_L', value=0.127275, min=0, max=0.5)
    params.add('c_max', value=130.901733, min=100, max=250)
    params.add('K_iN', value=0.12294103, min=0, max=5.064)
    params.add('K_iS', value=612.178130, min=300, max=700)
    params.add('K_iX', value=59.9737695, min=20, max=100)
    params.add('K_N', value=0.02000201, min=0, max=1)
    params.add('K_O', value=0.33085322, min=0, max=10)
    params.add('K_S', value=0.0430429, min=0, max=5)
    params.add('K_SL', value=0.02165744, min=0, max=1)
    params.add('m_s', value=0.02252332, min=0, max=2)
    params.add('r_L', value=0.47917813, min=0, max=1)
    params.add('V_evap', value=2.6 * 1e-03, min=0, max=10)
    params.add('Y_cs', value=0.682572, min=0, max=4)
    params.add('Y_ls', value=0.3574429, min=0, max=2)
    params.add('Y_xn', value=10.0, min=0, max=15)
    params.add('Y_xs', value=0.2385559, min=0, max=1)
    params.add('beta_LC_max', value=0.14255192, min=0, max=5)
    params.add('mu_max', value=0.3844627, min=0, max=2)
    params_copy = params.copy()

    beta_gibbs = pd.read_csv('/Users/hua.zheng/Research/PHD/project/BN-MDP/BN/beta_s5a1-R100-explore0.3-v1-modelrisk--ntime36-sigma10-2operations.txt', header=None, dtype=np.float64)
    v2_gibbs = pd.read_csv('/Users/hua.zheng/Research/PHD/project/BN-MDP/BN/v2_s5a1-R100-explore0.3-v1-modelrisk--ntime36-sigma10-2operations.txt', header=None,
                            dtype=np.float64)
    the_R = pd.read_csv('/Users/hua.zheng/Research/PHD/project/BN-MDP/BN/the_R_s5a1-R100-explore0.3-v1-modelrisk--ntime36-sigma10-2operations.txt', header=None, dtype=np.float64)
    tau2_R = pd.read_csv('/Users/hua.zheng/Research/PHD/project/BN-MDP/BN/tau2_R_s5a1-R100-explore0.3-v1-modelrisk--ntime36-sigma10-2operations.txt', header=None, dtype=np.float64)
    kap_R = pd.read_csv('/Users/hua.zheng/Research/PHD/project/BN-MDP/BN/kap_R_s5a1-R100-explore0.3-v1-modelrisk--ntime36-sigma10-2operations.txt', header=None, dtype=np.float64)
    lam_R = pd.read_csv('/Users/hua.zheng/Research/PHD/project/BN-MDP/BN/lam_R_s5a1-R100-explore0.3-v1-modelrisk--ntime36-sigma10-2operations.txt', header=None, dtype=np.float64)
    mu = pd.read_csv('/Users/hua.zheng/Research/PHD/project/BN-MDP/BN/mu_s5a1-R100-explore0.3-v1-modelrisk--ntime36-sigma10-2operations.txt', header=None, dtype=np.float64)
    sd = pd.read_csv('/Users/hua.zheng/Research/PHD/project/BN-MDP/BN/sd_s5a1-R100-explore0.3-v1-modelrisk--ntime36-sigma10-2operations.txt', header=None, dtype=np.float64)
    bn_post = bayesian_network_posterior(the_R.to_numpy(), tau2_R.to_numpy(), kap_R.to_numpy(), lam_R.to_numpy(),
                                         mu.to_numpy().flatten(), 5, 1, 39, beta_gibbs, v2_gibbs)
    p_beta, p_v2, mu = bn_post.posterior_sample(5000, useFixed=False)
    beta = np.apply_along_axis(np.mean, 2, p_beta)
    v2 = np.apply_along_axis(np.mean, 1, p_v2)
    bn = bayesian_network(mu, beta, v2, 1, 5, 39, True, mu, sd.to_numpy().flatten())

    ''' set up with the constraints '''
    def projection(y):
        theta = y.reshape((bn.n_time - 1, bn.num_state, bn.num_action))
        theta[:bn.n_time - 1 - 2, 0, :] = np.clip(theta[:bn.n_time - 1 - 2, 0, :], 0, 0.6)
        theta[:bn.n_time - 1 - 2, 1, :] = np.clip(theta[:bn.n_time - 1 - 2, 0, :], 0, 0.6)
        theta[0, 2, :] = np.clip(theta[0, 2, :], -0.2, 0.2)
        theta[:bn.n_time - 1 - 2, 2, :] = np.clip(theta[:bn.n_time - 1 - 2, 2, :], -0.4, 0.4)
        theta[:bn.n_time - 1 - 2, 3, :] = np.clip(theta[:bn.n_time - 1 - 2, 3, :], -0.2, 0.04)
        theta[:bn.n_time - 1 - 2, 4, :] = np.clip(theta[:bn.n_time - 1 - 2, 4, :], -1.4, 1)

        theta[bn.n_time - 1 - 2, 0, :] = np.clip(theta[bn.n_time - 1 - 2, 0, :], -0.1, 0.02)
        theta[bn.n_time - 1 - 2, 1, :] = np.clip(theta[bn.n_time - 1 - 2, 0, :], -0.02, 0.1)

        theta[bn.n_time - 1 - 1, 0, :] = np.clip(theta[bn.n_time - 1 - 1, 0, :], -0.0, 0.1)
        theta[bn.n_time - 1 - 1, 1, :] = np.clip(theta[bn.n_time - 1 - 1, 0, :], -0.1, 0.0)

        theta[bn.n_time - 1 - 2:, 2, :] = 0
        theta[bn.n_time - 1 - 2:, 3, :] = 0
        theta[bn.n_time - 1 - 2:, 4, :] = 0
        return theta.flatten()

    ''' projected gradient accent with posterior sampling '''

    cumulative_rewards = []
    cumulative_profits =[]
    final_purity = []
    final_titer = []
    total_feeding = []
    feed_profiles = []
    conv_trajectories = []
    patient = 15
    window = 15
    max_iter = 100
    algorithm = 'PGA'
    fixedBN = False
    for id in range(30):
        feed_profile_ddpg = []
        np.random.seed(1 + id)

        time_interval = 140 / (bn.n_time - 3 - 1)
        time_measurement = [time_interval * i for i in range(bn.n_time -3)]
        flag_estimate_reward_fun = False
        m = [0] * (bn.n_time - 1) + [-15]  # $/g
        b = np.transpose(np.array([[-0.13363 * 1000 * time_interval] * (bn.n_time - 3) + [-0.05] * 2]))  # $/g
        c = np.zeros(shape=(bn.n_time, bn.num_state))
        c[bn.n_time - 1, 0] = 1.28739  # product revenue
        c[bn.n_time - 1, 1] = - 1  # purification cost

        gradient = BN_gradient(m, b, c, bn, time_measurement, flag_estimate_reward_fun)
        theta = np.random.uniform(low=0., high=0.3, size=(bn.n_time - 1, bn.num_state, bn.num_action))
        grad = lambda x: - gradient.grad_f(x)
        f = lambda x: - gradient.func(x)

        process_model = mechanism()
        bio_env = two_unit_operations_env(process_model, bn, time_measurement, real_measurement, m, b, c, params)
        if not fixedBN:
            methods = {"PGA": cs.ProjectedGDWithPosterior(f,
                                                          grad,
                                                          projection,
                                                          ss.Backtracking(rule_type="Armijo", rho=0.5, beta=0.1,
                                                                          init_alpha=0.001),
                                                          bn_post,
                                                          bio_env,
                                                          sd.to_numpy().flatten(),
                                                          256,
                                                          patient,
                                                          window)
                       }
        else:
            methods = {"PGA": cs.ProjectedBNPG(f,
                                               grad,
                                               projection,
                                               ss.Backtracking(rule_type="Armijo", rho=0.5, beta=0.1,
                                                               init_alpha=0.001),
                                               bn_post,
                                               bio_env,
                                               sd.to_numpy().flatten(),
                                               256,
                                               patient,
                                               window)
                       }

        tol = -1000  # 1e-25
        for m_name in methods:
            print("\t", m_name)
            x = methods[m_name].solve(x0=theta.flatten(), max_iter=max_iter, tol=tol, disp=2)
        # gradient.states
        # [f(x) for x in methods[m_name].get_convergence()]

        theta = x.reshape((bn.n_time - 1, bn.num_state, bn.num_action))
        policy = Policy(theta, True)
        '''bn
        simulate new policy and existing policy with true model (ODEs)
        '''
        B = 50
        x0 = bn.initial_state_full

        simulator = fermentation_purification_simulator(bn, time_measurement, policy, real_measurement)
        state = simulator.g(time_measurement, x0, params)
        state = state[-1, :]
        feed_profile_ddpg = list(simulator.feed.values())
        total_feed_MDP = np.sum(feed_profile_ddpg)
        ammonium_sulphate, downstream_state, result2 = simulator.purification_processes(state)

        conv_trajectories.append(methods[m_name].convergence_reward)

        cur_titer = np.exp(downstream_state[0])
        cur_purity = np.exp(downstream_state[0]) / (np.exp(downstream_state[1]) + np.exp(downstream_state[0]))
        final_titer.append(cur_titer)
        final_purity.append(cur_purity)
        feed_profiles.append(list(simulator.feed.values()))
        total_feeding.append(total_feed_MDP * (140 / (bn.n_time - 3 - 1)))
        # final_reward = -0.13363 * 1000 * total_feed_MDP + 1.28739 * state[-1, 1]
        final_reward = -15 - 0.13363 * 1000 * total_feed_MDP - b[-1][0] * sum(ammonium_sulphate) \
                       + c[bn.n_time - 1, 0] * downstream_state[0] + c[bn.n_time - 1, 1] * downstream_state[1]
        final_profit = -15 - 0.13363 * 1000 * total_feed_MDP - b[-1][0] * sum(np.exp(ammonium_sulphate)) \
                       + c[bn.n_time - 1, 0] * np.exp(downstream_state[0]) + c[bn.n_time - 1, 1] * np.exp(
            downstream_state[1])

        cumulative_rewards.append(final_reward)
        cumulative_profits.append(final_profit)
        print('--------------------------------------------------')
        print(id)
        # print(policy.theta)
        print("mean final titer = {}".format(np.exp(downstream_state[0])))
        print("mean final purity = {}".format(np.exp(downstream_state[0])/(np.exp(downstream_state[1]) + np.exp(downstream_state[0]))))
        print("mean reward = {}".format(final_reward))
        print("mean profit = {}".format(final_profit))

    print('---------------------Summary-------------------------')
    print(np.mean(final_titer), np.std(final_titer))
    print(np.mean(final_purity), np.std(final_purity))
    print(np.mean(cumulative_rewards), np.std(cumulative_rewards))
    print(np.mean(cumulative_profits), np.std(cumulative_profits))


import collections
import io

import pytest

from hbp_spatial_backend import transform_graph

def test_transform_graph_sane_usage():
    tg = transform_graph.TransformGraph()
    tg.add_space('A')
    chain = tg.get_transform_chain('A', 'A')
    assert chain == []
    tg.add_space('B')
    chain = tg.get_transform_chain('A', 'B')
    assert chain is None
    tg.add_link('A', 'B', 'Transform A to B')
    chain = tg.get_transform_chain('A', 'B')
    assert chain == ['Transform A to B']
    chain = tg.get_transform_chain('B', 'A')
    assert chain is None
    tg.add_link('B', 'A', 'Transform B to A')
    tg.add_space('C')
    chain = tg.get_transform_chain('A', 'C')
    assert chain is None
    tg.add_link('B', 'C', 'Transform B to C')
    chain = tg.get_transform_chain('A', 'C')
    assert chain == ['Transform A to B', 'Transform B to C']
    tg.remove_link('A', 'B')
    chain = tg.get_transform_chain('A', 'C')
    assert chain is None

def test_transform_graph_cycle():
    tg = transform_graph.TransformGraph()
    # Monkeypatch the tested class to get reliable ordering (with a plain dict
    # Python's hash randomization makes the bug intermittent)
    tg.links = collections.OrderedDict()
    tg.add_space('A')
    tg.add_space('B')
    tg.add_space('C')
    tg.add_link('A', 'B', 'Transform A to B')
    tg.add_link('A', 'C', 'Transform A to C')
    tg.add_link('C', 'A', 'Transform C to A')
    chain = tg.get_transform_chain('A', 'B')
    assert chain == ['Transform A to B']

def test_transform_graph_api_errors():
    tg = transform_graph.TransformGraph()
    tg.add_space('A')
    with pytest.raises(ValueError):
        tg.add_space('A')
    with pytest.raises(KeyError):
        tg.get_transform_chain('A', 'B')
    with pytest.raises(KeyError):
        tg.get_transform_chain('B', 'A')
    tg.add_space('B')
    tg.add_link('A', 'B', 'Transform A to B')
    with pytest.raises(ValueError):
        tg.add_link('A', 'B', 'Duplicate link')

def test_export_graphviz():
    tg = transform_graph.TransformGraph()
    tg.add_space('A')
    tg.add_space('weird"*+chars')
    tg.add_link('A', 'weird"*+chars', 'Transform')
    buf = io.StringIO()
    tg.export_graphviz(buf)

def test_load_from_yaml():
    tg = transform_graph.TransformGraph.from_yaml(b'{A: {B: AtoB}, B: {}}')
    chain = tg.get_transform_chain('A', 'B')
    assert chain == ['AtoB']
    tg = transform_graph.TransformGraph.from_yaml(b'{A: {B: AtoB}}')
    chain = tg.get_transform_chain('A', 'B')
    assert chain == ['AtoB']
    with pytest.raises(ValueError):
        transform_graph.TransformGraph.from_yaml('[A, B, C]')


import numpy.typing as npt
import numpy as np
import voltools as vt
import logging
from scipy.ndimage import center_of_mass, zoom
from scipy.fft import rfftn, irfftn
from typing import Optional
from pytom_tm.weights import create_ctf, create_gaussian_low_pass, radial_average

plotting_available = True
try:
    import matplotlib.pyplot as plt
    import seaborn as sns
    sns.set(context='talk', style='ticks')
except ModuleNotFoundError:
    plotting_available = False

def generate_template_from_map(
        input_map: npt.NDArray[float],
        input_spacing: float,
        output_spacing: float,
        ctf_params: dict,
        center: bool = False,
        filter_to_resolution: Optional[float] = None,
        output_box_size: Optional[int] = None,
        display_filter: bool = False
) -> npt.NDArray[float]:

    # make the map a box with equal dimensions
    if len(set(input_map.shape)) != 1:
        diff = [max(input_map.shape) - s for s in input_map.shape]
        input_map = np.pad(
            input_map,
            tuple([(d // 2, d // 2 + d % 2) for d in diff]),
            mode='edge'
        )

    if filter_to_resolution is None or filter_to_resolution < (2 * output_spacing):
        logging.warning(f'Filter resolution is either not specified (in which case you can ignore this warning) '
                        f'or too low, changing to {2 * output_spacing}A (2 * output voxel size)')
        filter_to_resolution = 2 * output_spacing

    # extend volume to the desired output size before applying convolutions!
    if output_box_size is not None:
        logging.debug(f'size check {output_box_size} > {(input_map.shape[0] * input_spacing) // output_spacing}')
        if output_box_size > (input_map.shape[0] * input_spacing) // output_spacing:
            pad = int(output_box_size * (output_spacing / input_spacing)) - input_map.shape[0]
            logging.debug(f'pad with this number of zeros: {pad}')
            input_map = np.pad(
                input_map,
                (pad // 2, pad // 2 + pad % 2),
                mode='edge'
            )
        elif output_box_size < (input_map.shape[0] * input_spacing) // output_spacing:
            logging.warning('Could not set specified box size as the map would need to be cut and this might '
                            'result in loss of information of the structure. Please decrease the box size of the map '
                            'by hand (e.g. chimera)')

    if center:
        volume_center = np.divide(np.subtract(input_map.shape, 1), 2, dtype=np.float32)
        input_center_of_mass = center_of_mass(input_map)
        shift = np.subtract(volume_center, input_center_of_mass)
        input_map = vt.transform(input_map, translation=shift, device='cpu')

        logging.debug(f'center of mass, before was '
                      f'{np.round(input_center_of_mass, 2)} '
                      f'and after {np.round(center_of_mass(input_map), 2)}')

    # create ctf function and low pass gaussian if desired
    # for ctf the spacing of pixels/voxels needs to be in meters (not angstrom)
    ctf = 1 if ctf_params is None else create_ctf(input_map.shape, **ctf_params).astype(np.float32)
    lpf = create_gaussian_low_pass(input_map.shape, input_spacing, filter_to_resolution).astype(np.float32)

    if display_filter and plotting_available:
        q, average = radial_average(ctf * lpf)
        fig, ax = plt.subplots()
        ax.plot(q / len(q), average)
        ax.set_xlabel('Fraction of Nyquist')
        ax.set_ylabel('Contrast transfer')
        plt.show()
    elif display_filter and not plotting_available:
        logging.info('Plotting not possible as matplotlib is not installed')

    logging.info('Convoluting volume with filter and then downsampling.')
    return zoom(
        irfftn(rfftn(input_map) * ctf * lpf),
        input_spacing / output_spacing
    )


# фамилия | имя | номер телефона | должность | компания
'''
Телефонный справочник. Добавление контакта, удаление контакта,
редактирование контакта. Поиск по имени-фамилии, поиск по номеру,
поиск по частично набранному номеру
'''

class Contact:
    '''Work with contact'''
    def __init__(self, surname, name, phone_numbers, position, company):
        self.surname = surname
        self.name = name
        self.phone_numbers = phone_numbers
        self.phone_numbers_aray = phone_numbers.strip(',')
        self.position = position
        self.company = company
        self.contact = [self.surname, self.name, self.phone_numbers, self.position, self.company]

    def __str__(self):
        return f"{self.surname} {self.name} {self.phone_numbers} {self.position} {self.company}"

    def search(self, text):
        '''search text in contact'''
        for cell in self.contact:
            if text in cell:
                return True

    def modify(self,choice):
        ''' modify contact'''
        if choice == 1:
            self.surname = input('enter surname: ')
        elif choice == 2:
            self.name = input('enter name: ')
        elif choice == 3:
            self.phone_numbers = input('enter phone: ')
            self.phone_numbers_aray = phone_numbers.strip(',')
        elif choice == 4:
            self.position = input('enter position: ')
        elif choice == 5:
            self.company = input('enter company: ')

    def print(self):
        '''print contact'''
        print(f"{self.surname} {self.name} {self.phone_numbers} {self.position} {self.company}")

    def save(self):
        '''for save file'''
        return f"{self.surname} | {self.name} | {self.phone_numbers} | {self.position} | {self.company}\n"

class PhoneBook:
    '''work with phone book'''
    def __init__(self, file):
        self.file = file
        self.array = []
        with open(self.file, 'r') as phone_book:
            for line in phone_book:
                self.array.append(Contact(*line.strip().split(' | ')))

    @property
    def len(self):
        ''' quantity contacts'''
        return len(self.array)

    def print(self):
        '''print all contacts'''
        for n in self.array:
            print(n)

    def add(self,new_contact):
        '''add contact'''
        self.array.append(Contact(*new_contact))

    def remove(self,n):
        '''remove contact'''
        del self.array[n]

    def search(self, text):
        '''search and print contact'''
        for n in self.array:
            if n.search(text):
                n.print()

    def sorted(self):
        '''sort phonebook'''
        self.array = sorted(self.array, key = lambda d : d.surname + d.name)

    def save(self):
        '''save file'''
        with open(self.file, 'w') as phone_book:
            pass
        with open(self.file, 'a') as phone_book:
            temp_text = ''
            for contact in self.array:
                temp_text = temp_text + contact.save()
            phone_book.write(temp_text)

class Menu:
    '''menu for work with phonebook'''
    def __init__(self,file):
        self.phone_book = PhoneBook(file)

    def main(self):
        '''main menu'''
        while True:
            print('1 - print all contacts')
            print('2 - search contact')
            print('3 - add contact')
            print('4 - remove contact')
            print('5 - modify contact')
            print('6 - quit')
            choice = input('What we do now? ')
            if choice == '1':
                self.phone_book.print()
            elif choice == '2':
                text = input('enter text for search')
                self.phone_book.search(text)
            elif choice == '3':
                self.add_menu()
            elif choice == '4':
                contact_number = int(input('what number of contact you want delete? ')) - 1
                self.phone_book.remove(contact_number)
                self.phone_book.save()
            elif choice == '5':
                contact_number = int(input('what number of contact you want modify? ')) - 1
                self.modify_menu(contact_number)
                self.phone_book.save()
            elif choice == '6':
                break

    def add_menu(self):
        ''' use for add new contact'''
        new_contact = []
        new_contact.append(input('enter surname: '))
        new_contact.append(input('enter name: '))
        new_contact.append(input('enter phone numbers separate ",": '))
        new_contact.append(input('enter position: '))
        new_contact.append(input('enter company: '))
        self.phone_book.add(new_contact)
        self.phone_book.sorted()
        self.phone_book.save()

    def modify_menu(self, contact_number):
        '''use for modify contact'''
        print(self.phone_book.array[contact_number])
        print('what do you want modify?')
        print('1 surname')
        print('2 name')
        print('3 phone')
        print('4 position')
        print('5 company')
        choice = int(input())
        self.phone_book.array[contact_number].modify(choice)
        self.phone_book.sorted()
        self.phone_book.save()

menu = Menu('phonebook.txt')
menu.main()


import random

from bokeh.io import output_file, show
from bokeh.models import ColumnDataSource
from bokeh.palettes import Spectral6
from bokeh.plotting import figure
from bokeh.transform import factor_cmap

def graph(values, name):

    output_file("colormapped_bars.html")

    times = ['100', '1000', '10000', '100000']
    counts = [5, 3, 4, 2, 4, 6]

    source = ColumnDataSource(data=dict(times=times, values=values))

    p = figure(x_range=times, plot_height=1000, toolbar_location=None, title=name)
    p.vbar(x='times', top='values', width=0.9, source=source, legend_field="times", line_color='white', fill_color=factor_cmap('times', palette=Spectral6, factors=times))
    p.y_range.start = 0
    p.y_range.end = 1
    p.legend.orientation = "horizontal"
    p.legend.location = "top_center"

    show(p)

def tirar_dado(numero_de_tiros):
    secuencia_de_tiros = []

    for i in range(numero_de_tiros):
        tiro = random.randint(2, 12)

        secuencia_de_tiros.append(tiro)
    return secuencia_de_tiros

def main(numero_de_tiros, numero_de_intentos, numero_persona):
    tiros = []

    for _ in range(numero_de_intentos):
        secuencia_de_tiros = tirar_dado(numero_de_tiros)
        tiros.append(secuencia_de_tiros)

    tiros_acertados = 0
    tiros_no_acertados = 0
    for tiro in tiros:
        if numero_persona in tiro:
            tiros_acertados += 1
        else:
            tiros_no_acertados += 1

    probabilidad_de_tiros_acertados = tiros_acertados / numero_de_intentos
    probabilidad_de_tiros_no_acertados = tiros_no_acertados /numero_de_intentos
    # print(f"La probabilad de obtener por lo menos un 1 en un numero de intentos de {numero_de_intentos} es de {probabilidad_tiros_con_1}")
    print(f"La probabilad de obtener por lo menos un {numero_persona} en {numero_de_tiros} tiros\nSimulando este proceso {numero_de_intentos} veces es de {probabilidad_de_tiros_acertados}")
    print(f"La probabilad de NO obtener por lo menos un {numero_persona} en {numero_de_tiros} tiros\nSimulando este proceso {numero_de_intentos} veces es de {probabilidad_de_tiros_no_acertados}")
    print("=================================================================")
    return (probabilidad_de_tiros_acertados, probabilidad_de_tiros_no_acertados)

if __name__ == "__main__":
    all_prob_acert = []
    all_prob_no_acert = []

    numero_de_tiros = int(input("Cuantos veces quieres tirar los dos dados: "))
    numero_persona = int(input("A que numero le apuestas: "))
    while numero_persona > 12 or numero_persona < 2:
        numero_persona = int(input("El numero maximo con dos dados en 12 y el minimo es 2\n A que numero le apuestas:  "))
    numero_de_intentos = [100, 1000, 10000, 100000]

    for interval in numero_de_intentos:
        prob_acert, prob_no_acert = main(numero_de_tiros, interval, numero_persona)
        all_prob_acert.append(prob_acert)
        all_prob_no_acert.append(prob_no_acert)

    print("Graficando las probabilidades: ")
    print(f"Acertado: {all_prob_acert}")
    print(f"No Acertado: {all_prob_no_acert}")

    graph( all_prob_no_acert, f"Probabilidades no acertar con {numero_de_tiros} tiros" )
    graph( all_prob_acert, f"Probabilidades acertar con {numero_de_tiros} tiros" )

    print("=================================================================")


import numpy as np
import pyaudio
import mido
import itertools
import random

DEVICE = ""   # Midi keyboard name

SAMPLE_RATE = 44100
BUFFER_SIZE = 1024
VOICES = 100
DETUNE = 0.2

notes_dict = {}
lfo_dict = {}

p = pyaudio.PyAudio()
stream = p.open(format=pyaudio.paFloat32,
                channels=1,
                rate=SAMPLE_RATE,
                output=True,
                frames_per_buffer=BUFFER_SIZE)

def midi_to_freq(midi_note):
    A = 440
    if DETUNE:
        det = random.uniform(-DETUNE,DETUNE)
        return (A / 32) * (2 ** ((midi_note + det - 9) / 12))
    return (A / 32) * (2 ** ((midi_note - 9) / 12))

def gen_sine(freq, amp=1):
    increment = 2*np.pi*freq/SAMPLE_RATE
    return (np.sin(i)*amp for i in itertools.count(start=0, step=increment))

def get_samples():
    return [sum([next(osc) * np.float32(32767) \
            for _, osc in notes_dict.items()]) \
            for _ in range(BUFFER_SIZE)]

# Run the synth
in_port = mido.open_input(DEVICE)
while True:
    if notes_dict:
        sum_samples = get_samples()
        stream.write(np.float32(sum_samples).tobytes())

    msg = in_port.receive(block=False)
    if msg is not None:
        if msg.type == "note_on":
            if len(notes_dict)+1 > VOICES:
                notes_dict.pop(next(iter(notes_dict)))

            note = msg.note
            freq = midi_to_freq(note)
            amp = msg.velocity/127
            notes_dict[note] = gen_sine(freq, amp)

        if msg.type == "note_off" and msg.note in notes_dict.keys():
            del notes_dict[msg.note]

p.terminate()


"""
WSGI config for website project.

It exposes the WSGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/dev/howto/deployment/wsgi/
"""

# Print statements may help with gunicorn debugging

print("Importing: %s" % __file__)

import logging  # noqa: F402
import os  # noqa: F402

logging.basicConfig(
    format='%(asctime)s %(levelname)-7s %(thread)-5d %(filename)s:%(lineno)s | %(funcName)s | %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S',
)
logging.getLogger().setLevel(logging.DEBUG)
logging.disable(logging.NOTSET)

logging.info('Loading %s', __name__)

DJANGO_SETTINGS_MODULE = os.environ.setdefault("DJANGO_SETTINGS_MODULE", "website.settings")
print("DJANGO_SETTINGS_MODULE=", DJANGO_SETTINGS_MODULE)
from django.conf import settings  # noqa: F402 isort:skip

# determine where is the single absolute path that
# will be used as a reference point for other directories
SITE_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
logging.debug("SITE_ROOT: %s" % SITE_ROOT)
logging.debug("settings.DEBUG: %s" % settings.DEBUG)

# Obtain WSGIHandler
from django.core.wsgi import get_wsgi_application  # noqa: F402 isort:skip

application = get_wsgi_application()

logging.debug("application: %s" % application)


from __future__ import annotations

import asyncio
import datetime
import imghdr
import logging
import mimetypes
from pathlib import Path
from typing import Any, List, Optional, Sequence, Type, Union

import telegram.ext
import tzlocal
import validators
from apscheduler.schedulers.base import BaseScheduler
from telegram import Bot, Chat, InlineKeyboardMarkup, Message, ReplyKeyboardMarkup, Update
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.types import ODVInput
from telegram.constants import ChatAction, ParseMode
from telegram.ext import Application, CallbackQueryHandler, CommandHandler, MessageHandler, PicklePersistence
from telegram.ext._callbackcontext import CallbackContext
from telegram.ext._utils.types import BD, BT, CD, UD

from ._version import __raw_url__
from .models import BaseMessage, ButtonType, TypeCallback, call_function_EAFP, emoji_replace

logger = logging.getLogger(__name__)

class NavigationException(Exception):
    """Base exception."""

class TelegramMenuSession:
    """Session manager, send start message to each new user connecting to the bot."""

    # delays in seconds
    READ_TIMEOUT = 6
    CONNECT_TIMEOUT = 7
    START_MESSAGE = "start"

    def __init__(self, api_key: str, start_message: str = START_MESSAGE, persistence_path: str = "") -> None:
        """Initialize the session object.

        Args:
            api_key: Telegram bot API key
            start_message: text used to start a session, e.g. /start
        """
        if not isinstance(api_key, str):
            raise KeyError("API_KEY must be a string!")

        persistence = PicklePersistence(filepath=persistence_path if persistence_path else "arbitrarycallbackdatabot")
        self.application = (
            Application.builder().token(api_key).persistence(persistence).arbitrary_callback_data(True).build()
        )
        self.scheduler = self.application.job_queue.scheduler  # type: ignore

        self._api_key = api_key
        self.sessions: List[NavigationHandler] = []
        self.start_message_class: Optional[Type[BaseMessage]] = None
        self.start_message_args: Optional[List[Any]] = None
        self.navigation_handler_class: Optional[Type[NavigationHandler]] = None

        # on different commands - answer in Telegram
        self.application.add_handler(CommandHandler(start_message, self._send_start_message))
        self.application.add_handler(MessageHandler(telegram.ext.filters.TEXT, self._button_select_callback))
        self.application.add_handler(
            MessageHandler(telegram.ext.filters.StatusUpdate.WEB_APP_DATA, self._button_webapp_callback)
        )
        self.application.add_handler(CallbackQueryHandler(self._button_inline_select_callback))
        self.application.add_handler(telegram.ext.PollAnswerHandler(self._poll_answer))
        self.application.add_error_handler(self._msg_error_handler)
        self.application.add_handler(MessageHandler(telegram.ext.filters.LOCATION, self._get_location_handler))

    def start(
        self,
        start_message_class: Type[BaseMessage],
        start_message_args: Optional[List[Any]] = None,
        polling: bool = True,
        navigation_handler_class: Optional[Type[NavigationHandler]] = None,
        stop_signals: ODVInput[Sequence[int]] = DEFAULT_NONE,
    ) -> None:
        """Set the start message and run the dispatcher.

        Args:
            start_message_class: class used to create start message
            start_message_args: optional arguments passed to the start class
            polling: if True, start polling updates from Telegram
            navigation_handler_class: optional class used to extend the base NavigationHandler

        """
        self.start_message_class = start_message_class
        self.start_message_args = start_message_args
        self.navigation_handler_class = navigation_handler_class or NavigationHandler
        if not issubclass(start_message_class, BaseMessage):
            raise NavigationException("start_message_class must be a BaseMessage!")
        if start_message_args is not None and not isinstance(start_message_args, list):
            raise NavigationException("start_message_args is not a list!")
        if not issubclass(self.navigation_handler_class, NavigationHandler):
            raise NavigationException("navigation_handler_class must be a NavigationHandler!")

        if not self.scheduler.running:
            self.scheduler.start()
        if polling:
            self.application.run_polling(stop_signals=stop_signals)

    async def _send_start_message(self, update: Update, context: CallbackContext[BT, UD, CD, BD]) -> None:
        """Start main message, app choice."""
        chat = update.effective_chat
        if chat is None:
            raise NavigationException("Chat object was not created")
        if self.navigation_handler_class is None:
            raise NavigationException("Navigation Handler class not defined")
        session = self.navigation_handler_class(self.application.bot, chat, self.scheduler)
        self.sessions.append(session)
        if self.start_message_class is None:
            raise NavigationException("Message class not defined")
        if self.start_message_args is not None:
            start_message = self.start_message_class(session, message_args=self.start_message_args)
        else:
            start_message = self.start_message_class(session)
        await session.goto_menu(start_message, context)

    def get_session(self, chat_id: int = 0) -> Optional[NavigationHandler]:
        """Get session from list."""
        sessions = [x for x in self.sessions if chat_id in (x.chat_id, 0)]
        if not sessions:
            return None
        return sessions[0]

    async def _get_location_handler(self, update: Update, context: CallbackContext[BT, UD, CD, BD]) -> None:
        if update.effective_chat is None or update.message is None or update.message.location is None:
            raise NavigationException("Incorrect session, location can't be updated")
        session = self.get_session(update.effective_chat.id)
        if session is not None:
            session.location = update.message.location

    async def _button_select_callback(self, update: Update, context: CallbackContext[BT, UD, CD, BD]) -> None:
        """Menu message main entry point."""
        if update.effective_chat is None or update.message is None:
            raise NavigationException("Chat object was not created")
        session = self.get_session(update.effective_chat.id)
        if session is None:
            await self._send_start_message(update, context)
            return
        if update.message.text:
            await session.select_menu_button(update.message.text, context)

    async def _poll_answer(self, update: Update, _: CallbackContext[BT, UD, CD, BD]) -> None:
        """Entry point for poll selection."""
        if update.effective_user is None or update.poll_answer is None:
            raise NavigationException("User object was not created")
        session = next((x for x in self.sessions if x.user_name == update.effective_user.first_name), None)
        if session:
            await session.poll_answer(update.poll_answer.option_ids[0])

    async def _button_inline_select_callback(self, update: Update, context: CallbackContext[BT, UD, CD, BD]) -> None:
        """Execute inline callback of an BaseMessage."""
        if update.effective_chat is None or update.callback_query is None:
            raise NavigationException("Chat object was not created")
        session = self.get_session(update.effective_chat.id)
        if session is None:
            await self._send_start_message(update, context)
            return
        if update.callback_query.data and update.callback_query.id:
            await session.app_message_button_callback(update.callback_query.data, update.callback_query.id, context)

    async def _button_webapp_callback(self, update: Update, context: CallbackContext[BT, UD, CD, BD]) -> None:
        """Execute webapp callback."""
        if (
            update.effective_chat is None
            or update.effective_message is None
            or update.effective_message.web_app_data is None
        ):
            raise NavigationException("Chat object was not created")
        session = self.get_session(update.effective_chat.id)
        if session is None:
            await self._send_start_message(update, context)
            return
        await session.app_message_webapp_callback(
            update.effective_message.web_app_data.data, update.effective_message.web_app_data.button_text
        )

    @staticmethod
    async def _msg_error_handler(update: object, context: CallbackContext[BT, UD, CD, BD]) -> None:  # type: ignore
        """Log Errors caused by Updates."""
        if not isinstance(update, Update):
            raise NavigationException("Incorrect update object")
        error_message = str(context.error) if update is None else f"Update {update.update_id} - {str(context.error)}"
        logger.error(error_message)

    async def broadcast_message(self, message: str, notification: bool = True) -> List[telegram.Message]:
        """Broadcast simple message without keyboard markup to all sessions."""
        messages = []
        for session in self.sessions:
            msg = await session.send_message(message, notification=notification)
            if msg is not None:
                messages.append(msg)
        return messages

    async def broadcast_picture(self, picture_path: str, notification: bool = True) -> List[telegram.Message]:
        """Broadcast picture to all sessions."""
        messages = []
        for session in self.sessions:
            msg = await session.send_photo(picture_path, notification=notification)
            if msg is not None:
                messages.append(msg)
        return messages

    async def broadcast_sticker(self, sticker_path: str, notification: bool = True) -> List[telegram.Message]:
        """Broadcast picture to all sessions."""
        messages = []
        for session in self.sessions:
            msg = await session.send_sticker(sticker_path, notification=notification)
            if msg is not None:
                messages.append(msg)
        return messages

class NavigationHandler:
    """Navigation handler for Telegram application."""

    POLL_DEADLINE = 10  # seconds
    MESSAGE_CHECK_TIMEOUT = 10  # seconds
    CONNECTION_POOL_SIZE = 8

    def __init__(self, bot: Bot, chat: Chat, scheduler: BaseScheduler) -> None:
        """Init NavigationHandler class."""
        self._bot = bot
        self._poll: Optional[Message] = None
        self._poll_callback: Optional[TypeCallback] = None

        self.scheduler = scheduler
        self.chat_id = chat.id
        self.user_name = chat.first_name
        self.poll_name = f"poll_{self.user_name}"
        self.location: Optional[telegram.Location] = None

        logger.info(f"Opening chat with user {self.user_name}")

        self._menu_queue: List[BaseMessage] = []  # list of menus selected by user
        self._message_queue: List[BaseMessage] = []  # list of application messages sent

        # check if messages have expired every MESSAGE_CHECK_TIMEOUT seconds
        scheduler.add_job(
            self._expiry_date_checker,
            "interval",
            id="state_nav_update",
            seconds=self.MESSAGE_CHECK_TIMEOUT,
            replace_existing=True,
        )

    async def _expiry_date_checker(self) -> None:
        """Check expiry date of message and delete if expired."""
        for message in self._message_queue:
            if message.has_expired():
                await self._delete_queued_message(message)

        # go back to home after sub-menu message has expired
        if len(self._menu_queue) >= 2 and self._menu_queue[-1].has_expired():
            await self.goto_home()

    async def delete_message(self, message_id: int) -> None:
        """Delete a message from its id."""
        await self._bot.delete_message(chat_id=self.chat_id, message_id=message_id)

    async def _delete_queued_message(self, message: BaseMessage) -> None:
        """Delete a message, remove from queue."""
        message.kill_message()
        if message in self._message_queue:
            self._message_queue.remove(message)
            await self.delete_message(message.message_id)
        del message

    async def goto_menu(
        self, menu_message: BaseMessage, context: Optional[CallbackContext[BT, UD, CD, BD]] = None
    ) -> int:
        """Send menu message and add to queue."""
        content = await menu_message.get_updated_content(context)
        logger.info(f"Opening menu {menu_message.label}")
        keyboard = menu_message.gen_keyboard_content()
        if menu_message.picture:
            message = await self.send_photo(
                menu_message.picture, notification=menu_message.notification, keyboard=keyboard, caption=content
            )
        else:
            message = await self.send_message(content, keyboard, notification=menu_message.notification)
        if message is None:
            return -1  # message was not sent, abort
        menu_message.is_alive()
        menu_message.message_id = message.message_id
        self._menu_queue.append(menu_message)
        return message.message_id

    async def goto_home(self, context: Optional[CallbackContext[BT, UD, CD, BD]] = None) -> int:
        """Go to home menu, empty menu_queue."""
        if not self._menu_queue:
            return -1
        if len(self._menu_queue) == 1:
            # already at 'home' level
            return self._menu_queue[0].message_id
        menu_previous = self._menu_queue.pop()
        while self._menu_queue:
            menu_previous = self._menu_queue.pop()
        return await self.goto_menu(menu_previous, context)

    @staticmethod
    def filter_unicode(input_string: str) -> str:
        """Remove non-unicode characters from input string."""
        return input_string.encode("ascii", "ignore").decode("utf-8")

    async def _send_app_message(
        self, message: BaseMessage, label: str, context: Optional[CallbackContext[BT, UD, CD, BD]] = None
    ) -> int:
        """Send an application message."""
        content = await message.get_updated_content(context)
        # if message with this label already exist in message_queue, delete it and replace it
        info_message = self.filter_unicode(f"Send message '{message.label}': '{label}'")
        logger.info(str(info_message))
        if "_" not in message.label:
            message.label = f"{message.label}_{label}"

        # delete message if already displayed
        message_existing = self.get_message(message.label)
        if message_existing is not None:
            await self._delete_queued_message(message)

        message.is_alive()

        keyboard = message.gen_inline_keyboard_content()
        if message.picture:
            msg = await self.send_photo(
                message.picture, notification=message.notification, caption=content, keyboard=keyboard
            )
        else:
            msg = await self.send_message(content, keyboard, message.notification)
        if msg is None:
            return -1  # message was not sent, abort
        message.message_id = msg.message_id
        self._message_queue.append(message)

        message.content_previous = content
        message.keyboard_previous = message.keyboard.copy()
        return message.message_id

    async def send_message(
        self,
        content: str,
        keyboard: Optional[Union[ReplyKeyboardMarkup, InlineKeyboardMarkup]] = None,
        notification: bool = True,
    ) -> telegram.Message:
        """Send a text message with html formatting."""
        return await self._bot.send_message(
            chat_id=self.chat_id,
            text=content,
            parse_mode=ParseMode.HTML,
            reply_markup=keyboard,
            disable_notification=not notification,
        )

    async def edit_message(
        self, message: BaseMessage, context: Optional[CallbackContext[BT, UD, CD, BD]] = None
    ) -> bool:
        """Edit an inline message asynchronously."""
        message_updt = self.get_message(message.label)
        if message_updt is None:
            return False

        # check if content and keyboard have changed since previous message
        content = await message_updt.get_updated_content(context)
        if not self._message_check_changes(message_updt, content):
            return False

        keyboard_format = message_updt.gen_inline_keyboard_content()
        try:
            if message_updt.picture:
                await self._bot.edit_message_caption(
                    caption=content,
                    chat_id=self.chat_id,
                    message_id=message_updt.message_id,
                    parse_mode=ParseMode.HTML,
                    reply_markup=keyboard_format,
                )
            else:
                await self._bot.edit_message_text(
                    text=content,
                    chat_id=self.chat_id,
                    message_id=message_updt.message_id,
                    parse_mode=ParseMode.HTML,
                    reply_markup=keyboard_format,
                )
        except telegram.error.BadRequest as error:
            logger.error(error)
            return False
        return True

    @staticmethod
    def _message_check_changes(message: BaseMessage, content: str) -> bool:
        """Check is message content and keyboard has changed since last edit."""
        content_identical = content == message.content_previous
        keyboard_identical = [y.label for x in message.keyboard_previous for y in x] == [
            y.label for x in message.keyboard for y in x
        ]
        if content_identical and keyboard_identical:
            return False
        message.content_previous = content
        message.keyboard_previous = message.keyboard.copy()
        return True

    async def select_menu_button(
        self, label: str, context: Optional[CallbackContext[BT, UD, CD, BD]] = None
    ) -> Optional[int]:  # noqa: C901
        """Select menu button using label."""
        msg_id = 0
        if label == "Back":
            if len(self._menu_queue) == 1:
                # already at 'home' level
                return self._menu_queue[0].message_id
            menu_previous = self._menu_queue.pop()  # delete actual menu
            if self._menu_queue:
                menu_previous = self._menu_queue.pop()
            return await self.goto_menu(menu_previous, context)
        if label == "Home":
            return await self.goto_home(context)

        for menu_item in self._menu_queue[::-1]:
            btn = menu_item.get_button(label)
            if not btn:
                continue
            if isinstance(btn.callback, BaseMessage):
                if btn.callback.inlined:
                    msg_id = await self._send_app_message(btn.callback, label, context)
                    if btn.callback.home_after:
                        msg_id = await self.goto_home(context)
                else:
                    msg_id = await self.goto_menu(btn.callback, context)
            elif btn.callback is not None and hasattr(btn.callback, "__call__"):
                if btn.args is not None:
                    await call_function_EAFP(btn.callback, context, btn.args)
                else:
                    await call_function_EAFP(btn.callback, context)
            return msg_id

        # label does not match any sub-menu, just process the user input
        await self.capture_user_input(label, context)
        return None

    async def capture_user_input(self, label: str, context: Optional[CallbackContext[BT, UD, CD, BD]] = None) -> None:
        """Process the user input in the last message updated."""
        last_menu_message = self._menu_queue[-1]
        if self._message_queue:
            for last_app_message in self._message_queue[::-1]:
                if last_app_message.time_alive > last_menu_message.time_alive:
                    last_menu_message = last_app_message
        await last_menu_message.text_input(label, context)

    async def app_message_webapp_callback(self, webapp_data: str, button_text: str) -> None:
        """Execute the callback associated to this webapp."""
        last_menu = self._menu_queue[-1]
        webapp_message = next(iter(y for x in last_menu.keyboard for y in x if y.label == button_text), None)
        if webapp_message is not None and callable(webapp_message.callback):
            if asyncio.iscoroutinefunction(webapp_message.callback):
                html_response = await webapp_message.callback(webapp_data)
            else:
                html_response = webapp_message.callback(webapp_data)
            await self.send_message(html_response, notification=webapp_message.notification)

    async def app_message_button_callback(
        self, callback_label: str, callback_id: str, context: Optional[CallbackContext[BT, UD, CD, BD]] = None
    ) -> None:
        """Entry point to execute an action after message button selection."""
        label_message, label_action = callback_label.split(BaseMessage.SEPARATOR)
        log_message = self.filter_unicode(f"Received action request from '{label_message}': '{label_action}'")
        logger.info(log_message)
        message = self.get_message(label_message)
        if message is None:
            logger.error(f"Message with label {label_message} not found, return")
            return
        btn = message.get_button(label_action)

        if btn is None:
            logger.error(f"No button found with label {label_action}, return")
            return

        if btn.btype in [ButtonType.PICTURE, ButtonType.STICKER]:
            # noinspection PyTypeChecker
            await self._bot.send_chat_action(chat_id=self.chat_id, action=ChatAction.UPLOAD_PHOTO)
        elif btn.btype == ButtonType.MESSAGE:
            # noinspection PyTypeChecker
            await self._bot.send_chat_action(chat_id=self.chat_id, action=ChatAction.TYPING)
        elif btn.btype == ButtonType.POLL:
            await self.send_poll(question=btn.args[0], options=btn.args[1])
            self._poll_callback = btn.callback
            await self._bot.answer_callback_query(callback_id, text="Select an answer...")
            return

        if not callable(btn.callback):
            return

        if btn.args is not None:
            action_status = await call_function_EAFP(btn.callback, context, btn.args)
        else:
            action_status = await call_function_EAFP(btn.callback, context)

        # send picture if custom label found
        if btn.btype == ButtonType.PICTURE:
            await self.send_photo(picture_path=action_status, notification=btn.notification)
            await self._bot.answer_callback_query(callback_id, text="Picture sent!")
            return
        if btn.btype == ButtonType.STICKER:
            await self.send_sticker(sticker_path=action_status, notification=btn.notification)
            await self._bot.answer_callback_query(callback_id, text="Sticker sent!")
            return
        if btn.btype == ButtonType.MESSAGE:
            await self.send_message(action_status, notification=btn.notification)
            await self._bot.answer_callback_query(callback_id, text="Message sent!")
            return
        await self._bot.answer_callback_query(callback_id, text=action_status)

        # update expiry period and update
        message.is_alive()
        await self.edit_message(message, context)

    @staticmethod
    def _sticker_check_replace(sticker_path: str) -> Union[str, bytes]:
        """Check if sticker is correct file type."""
        try:
            if not sticker_path.lower().endswith(".webp"):
                raise ValueError("Sticker has no .webp format")
            if validators.url(sticker_path):
                # todo: add check if url exists
                return sticker_path
            if Path(sticker_path).is_file() and imghdr.what(sticker_path):
                with open(sticker_path, "rb") as file_h:
                    return file_h.read()
            raise ValueError("Path is not a picture")
        except ValueError:
            url_default = f"{__raw_url__}/resources/stats_default.webp"
            logger.error(f"Picture path '{sticker_path}' is invalid, replacing with default {url_default}")
            return url_default

    @staticmethod
    def _picture_check_replace(picture_path: str) -> Union[str, bytes]:
        """Check if the given picture path or uri is correct, replace by default if not."""
        try:
            if validators.url(picture_path):
                # check if the url has image format
                mimetype, _ = mimetypes.guess_type(picture_path)
                if mimetype and mimetype.startswith("image"):
                    return picture_path
                raise ValueError("Url is not a picture")
            if Path(picture_path).is_file() and imghdr.what(picture_path):
                with open(picture_path, "rb") as file_h:
                    return file_h.read()
            raise ValueError("Path is not a picture")
        except ValueError:
            url_default = f"{__raw_url__}/resources/stats_default.png"
            logger.error(f"Picture path '{picture_path}' is invalid, replacing with default {url_default}")
            return url_default

    async def send_photo(
        self,
        picture_path: str,
        notification: bool = True,
        caption: str = "",
        keyboard: Optional[Union[ReplyKeyboardMarkup, InlineKeyboardMarkup]] = None,
    ) -> Optional[telegram.Message]:
        """Send a picture."""
        picture_obj = self._picture_check_replace(picture_path=picture_path)
        try:
            return await self._bot.send_photo(
                chat_id=self.chat_id,
                photo=picture_obj,
                caption=caption,
                reply_markup=keyboard,
                disable_notification=not notification,
                parse_mode=ParseMode.HTML,
            )
        except telegram.error.BadRequest as error:
            logger.error(f"Failed to send picture {picture_path}: {error}")
        return None

    async def send_sticker(self, sticker_path: str, notification: bool = True) -> Optional[telegram.Message]:
        """Send a picture."""
        sticker_obj = self._sticker_check_replace(sticker_path=sticker_path)
        try:
            return await self._bot.send_sticker(
                chat_id=self.chat_id, sticker=sticker_obj, disable_notification=not notification
            )
        except telegram.error.BadRequest as error:
            logger.error(f"Failed to send picture {sticker_path}: {error}")
        return None

    def get_message(self, label_message: str) -> Optional[BaseMessage]:
        """Get message from message_queue matching attribute label_message."""
        return next(iter(x for x in self._message_queue if x.label == label_message), None)

    async def send_poll(self, question: str, options: List[str]) -> None:
        """Send poll to user with question and options."""
        if self.scheduler.get_job(self.poll_name) is not None:
            await self.poll_delete()
        options = [emoji_replace(x) for x in options]
        self._poll = await self._bot.send_poll(
            chat_id=self.chat_id,
            question=emoji_replace(question),
            options=options,
            is_anonymous=False,
            open_period=self.POLL_DEADLINE,
        )
        self.scheduler.add_job(
            self.poll_delete,
            "date",
            id=self.poll_name,
            next_run_time=datetime.datetime.now(tz=tzlocal.get_localzone())
            + datetime.timedelta(seconds=self.POLL_DEADLINE + 1),
            replace_existing=True,
        )

    async def poll_delete(self) -> None:
        """Run when poll timeout has expired."""
        if self._poll is not None and self._poll.poll is not None:
            try:
                logger.info(f"Deleting poll '{self._poll.poll.question}'")
                await self._bot.delete_message(chat_id=self.chat_id, message_id=self._poll.message_id)
            except telegram.error.BadRequest:
                logger.error(f"Poll message {self._poll.message_id} already deleted")

    async def poll_answer(self, answer_id: int) -> None:
        """Run when poll message is received."""
        if (
            self._poll is None
            or self._poll.poll is None
            or self._poll_callback is None
            or not callable(self._poll_callback)
        ):
            logger.error("Poll is not defined")
            return

        answer_ascii = self._poll.poll.options[answer_id].text.encode("ascii", "ignore").decode()
        logger.info(f"{self.user_name}'s answer to question '{self._poll.poll.question}' is '{answer_ascii}'")
        if asyncio.iscoroutinefunction(self._poll_callback):
            await self._poll_callback(self._poll.poll.options[answer_id].text)
        else:
            self._poll_callback(self._poll.poll.options[answer_id].text)
        await asyncio.sleep(1)
        await self.poll_delete()

        if self.scheduler.get_job(self.poll_name) is not None:
            self.scheduler.remove_job(self.poll_name)
        self._poll = None


import sys;read=sys.stdin.readline
N = int(read())
x = []
y = []
for _ in range(N):
    a,b = map(float,read().split())
    x.append(a)
    y.append(b)
x.append(x[-1])
y.append(y[-1])
plus = 0.0
minus = 0.0
for i in range(N):
    plus += x[i]*y[i+1]
for i in range(1,N+1):
    minus -= x[i]*y[i-1]
print(round(abs(plus-minus)/2,1))


import sys
input = sys.stdin.readline

N, C = map(int, input().split())
house = []
for _ in range(N):
    house.append(int(input().rstrip()))

house.sort()

start = 1
end = house[-1] - house[0]
ans = 0

while start <= end:
    mid = (start + end) // 2
    now = house[0]
    cnt = 1
    for x in house:
        if x >= mid + now:
            cnt += 1
            now = x
    if cnt >= C:
        ans = mid
        start = mid + 1
    else:
        end = mid - 1
print(ans)

백준/Gold/2110. 공유기 설치/공유기 설치.py


"""
Based on: https://docs.tudat.space/en/latest/_src_getting_started/_src_examples/notebooks/propagation/perturbed_satellite_orbit.html?highlight=ground#Ground-track
"""

# Load standard modules
import numpy as np

import matplotlib
from matplotlib import pyplot as plt

# Load tudatpy modules
from tudatpy.kernel.interface import spice
from tudatpy.kernel import numerical_simulation
from tudatpy.kernel.numerical_simulation import environment_setup, propagation_setup
from tudatpy.kernel.astro import element_conversion
from tudatpy.kernel import constants
from tudatpy.util import result2array

import satellite

class tudat_environment:
    def __init__(self, epochstart: float = 0):
        self.initial_state = None
        self.time_hours = None
        self.dep_vars_array = None
        self.states_array = None
        self.propagator_settings = None
        self.acceleration_models = None
        self.dependent_variables_to_save = []
        self.initial_states_settings = None
        self.initial_states = None
        self.acceleration_settings = None
        self.central_bodies = None
        self.bodies_to_propagate = None

        # Load spice kernels
        spice.load_standard_kernels()

        # Set simulation start and end epochs
        self.simulation_start_epoch = epochstart   ## January 1 2035
        self.simulation_end_epoch = constants.JULIAN_DAY

        # Define string names for bodies to be created from default.
        self.bodies_to_create = ["Sun", "Earth", "Moon"] #"Mars", "Venus"

        # Use "Earth"/"J2000" as global frame origin and orientation.
        self.global_frame_origin = "Earth"
        self.global_frame_orientation = "J2000"

        # Create default body settings, usually from `spice`.
        self.body_settings = environment_setup.get_default_body_settings(
            self.bodies_to_create,
            self.global_frame_origin,
            self.global_frame_orientation)

        # Create system of selected celestial bodies
        self.bodies = environment_setup.create_system_of_bodies(self.body_settings)

    def create_sat(self, sat_object, name: str):
        # Create vehicle objects.
        self.bodies.create_empty_body(name)
        self.bodies.get(name).dry_mass = sat_object.dry_mass

    def set_up_aerodynamics(self,
                            sat: str,
                            reference_area: float, ## Initial vol
                            drag_coefficient: float  ## Initital drag coefficient
                            ):
        # Create aerodynamic coefficient interface settings, and add to vehicle
        aero_coefficient_settings = environment_setup.aerodynamic_coefficients.constant(
            reference_area, [drag_coefficient, 0, 0]
        )
        environment_setup.add_aerodynamic_coefficient_interface(
            self.bodies, sat, aero_coefficient_settings)

    def set_up_solar_pressure(self,
                              sat: str,
                              reference_area_radiation: float,
                              radiation_pressure_coefficient: float
                              ):
        # Create radiation pressure settings, and add to vehicle
        occulting_bodies = ["Earth"]
        radiation_pressure_settings = environment_setup.radiation_pressure.cannonball(
            "Sun", reference_area_radiation, radiation_pressure_coefficient, occulting_bodies
        )
        environment_setup.add_radiation_pressure_interface(
            self.bodies, sat, radiation_pressure_settings)

    def propagation_setup(self, sats: list):
        self.bodies_to_propagate = sats
        self.central_bodies = ["Earth"]

    def set_up_acceleration(self, sats: list):
        # Define accelerations acting on satellite by Sun and Earth.
        accelerations_settings = dict(
            #Sun=[
                #propagation_setup.acceleration.cannonball_radiation_pressure(),
            #    propagation_setup.acceleration.point_mass_gravity()
            #],
            Earth=[
                propagation_setup.acceleration.spherical_harmonic_gravity(1, 5),
                #propagation_setup.acceleration.aerodynamic()
            ]#,
            #Moon=[
            #    propagation_setup.acceleration.point_mass_gravity()
            #]#, ## Should be removed due to insignificance compared to the others:
            # Mars=[
            #     propagation_setup.acceleration.point_mass_gravity()
            # ],
            # Venus=[
            #     propagation_setup.acceleration.point_mass_gravity()
            # ]
        )

        # Create global accelerations settings dictionary.
        self.acceleration_settings = {}
        for sat in sats:
            self.acceleration_settings[sat] = accelerations_settings

        # Create acceleration models.
        self.acceleration_models = propagation_setup.create_acceleration_models(
            self.bodies,
            self.acceleration_settings,
            self.bodies_to_propagate,
            self.central_bodies)

    def set_up_initial_states(self, sats: list):
        self.initial_states = np.empty((len(sats), 6))

    def set_initial_state(self, sat_object, sat: str, sats: list):
        # Set initial conditions for the satellite that will be
        # propagated in this simulation. The initial conditions are given in
        # Keplerian elements and later on converted to Cartesian elements
        earth_gravitational_parameter = self.bodies.get("Earth").gravitational_parameter

        self.initial_state = element_conversion.keplerian_to_cartesian_elementwise(
            gravitational_parameter=earth_gravitational_parameter,
            semi_major_axis=sat_object.a,
            eccentricity=sat_object.e,
            inclination=sat_object.i,
            argument_of_periapsis=sat_object.omega,
            longitude_of_ascending_node=sat_object.OMEGA,
            true_anomaly=sat_object.nu,
        )

        self.initial_states[np.where(sats == sat)] = self.initial_state

    def finalise_initial_states(self):
        self.initial_states_settings = np.concatenate(self.initial_states)

    def define_vars_to_store(self, sat: str):
        # Define list of dependent variables to save
        self.dependent_variables_to_save = [
            propagation_setup.dependent_variable.total_acceleration(sat),
            propagation_setup.dependent_variable.keplerian_state(sat, "Earth"),
            propagation_setup.dependent_variable.latitude(sat, "Earth"),
            propagation_setup.dependent_variable.longitude(sat, "Earth"),
            #propagation_setup.dependent_variable.single_acceleration_norm(
            #    propagation_setup.acceleration.point_mass_gravity_type, sat, "Sun"
            #),
            #propagation_setup.dependent_variable.single_acceleration_norm(
            #    propagation_setup.acceleration.point_mass_gravity_type, sat, "Moon"
            #),
            # propagation_setup.dependent_variable.single_acceleration_norm(
            #     propagation_setup.acceleration.point_mass_gravity_type, sat, "Mars"
            # ),
            # propagation_setup.dependent_variable.single_acceleration_norm(
            #     propagation_setup.acceleration.point_mass_gravity_type, sat, "Venus"
            # ),
            propagation_setup.dependent_variable.single_acceleration_norm(
                propagation_setup.acceleration.spherical_harmonic_gravity_type, sat, "Earth"
            )#,
            #propagation_setup.dependent_variable.single_acceleration_norm(
            #    propagation_setup.acceleration.aerodynamic_type, sat, "Earth"
            #)#,
            #propagation_setup.dependent_variable.single_acceleration_norm(
            #    propagation_setup.acceleration.cannonball_radiation_pressure_type, sat, "Sun"
            #)
        ]

    def define_propagator_settings(self):
        # Create termination settings
        termination_condition = propagation_setup.propagator.time_termination(self.simulation_end_epoch)

        # Create numerical integrator settings
        fixed_step_size = 1.0
        integrator_settings = propagation_setup.integrator.runge_kutta_4(fixed_step_size)

        # Create propagation settings
        self.propagator_settings = propagation_setup.propagator.translational(
            self.central_bodies,
            self.acceleration_models,
            self.bodies_to_propagate,
            self.initial_state,
            self.simulation_start_epoch,
            integrator_settings,
            termination_condition,
            output_variables=self.dependent_variables_to_save
        )

    def simulate(self):
        # Create simulation object and propagate the dynamics
        dynamics_simulator = numerical_simulation.create_dynamics_simulator(
            self.bodies, self.propagator_settings
        )

        # Extract the resulting state and dependent variable history and convert it to an ndarray
        states = dynamics_simulator.state_history
        self.states_array = result2array(states)
        dep_vars = dynamics_simulator.dependent_variable_history
        self.dep_vars_array = result2array(dep_vars)

        self.time_hours = self.dep_vars_array[:, 0]/3600

    def plot_ground_track(self, sat_objects: list | np.ndarray, show_plot: bool = True, subset: int | None = None):
        for sat in sat_objects:
            if subset is None:
                latitude = np.rad2deg(self.dep_vars_array[:, 10]) + np.rad2deg(sat.OMEGA)
                longitude = np.rad2deg(self.dep_vars_array[:, 11])
            else:
                latitude = np.rad2deg(self.dep_vars_array[:, 10])[:subset] + np.rad2deg(sat.OMEGA)
                longitude = np.rad2deg(self.dep_vars_array[:, 11])[:subset]

            plt.scatter(longitude, latitude, s=1)

        if show_plot:
            plt.show()

    def create_plot(self, hours: float | int | None = None):
        if hours is None:
            subset = None
        else:
            subset = int(len(self.time_hours) / 24 * hours)
        plt.figure(figsize=(9, 5))
        plt.title(f"Ground track of satellite over {hours} hours")
        plt.xlabel('Longitude [deg]')
        plt.ylabel('Latitude [deg]')

        plt.yticks(np.arange(-91, 91, step=45))
        plt.xlim([-180, 180])
        plt.grid()
        plt.tight_layout()

        return subset

def set_up_and_run_tudat(sat_objects):
    sat1 = sat_objects[0]
    tudat_env = tudat_environment()
    tudat_env.create_sat(sat1, sat1.name)
    tudat_env.set_up_aerodynamics(sat1.name, sat1.reference_area, sat1.drag_coefficient)
    tudat_env.set_up_solar_pressure(sat1.name, sat1.radiation_reference_area, sat1.solar_pressure_coefficient)
    tudat_env.propagation_setup([sat1.name])
    tudat_env.set_up_acceleration([sat1.name])
    tudat_env.set_up_initial_states([sat1.name])
    tudat_env.set_initial_state(sat1, sat1.name, [sat1.name])
    tudat_env.finalise_initial_states()
    tudat_env.define_vars_to_store(sat1.name)
    tudat_env.define_propagator_settings()
    tudat_env.simulate()
    return tudat_env

if __name__ == "__main__":
    sat = satellite.load_sat(1)
    sat.reference_area = 2
    sat.drag_coefficient = 1
    sat.radiation_reference_area = 3
    sat.solar_pressure_coefficient = 0.5
    sat.mass = 1000
    print(sat.__dict__)
    tudat_env = set_up_and_run_tudat([sat])