text: ########################
  ## Author: Reinan Br. ##
  ## Date_init:01/07/21 ##
  ########################
  from qfunction import q_sin,q_exp,q_cos,radian,limit
  import numpy as np
  from numpy import sqrt,array
  from math import atan
  import numpy as np
  ############ quantum equations #############
  def q_rho(u,q=1,cpx=False,israd=True):
  	u = radian(u) if(not israd) else u
  	u = u*1j
  	if cpx:
  		return sqrt(q_exp(u,q)*q_exp(-u,q))
  	else:
  		return (sqrt(q_exp(u,q)*q_exp(-u,q))).real
  
  
  
  def q_psi(theta,gamma,q=1,israd=True):
      import numpy as np
      gamma = radian(gamma) if(not israd) else gamma
      theta = radian(theta) if(not israd) else theta
      theta = 1j*theta/2
      gamma = gamma/2
      #print(gamma)
      #gamma = np.array(gamma)
      if(type(gamma)==np.ndarray):
          gamma[np.abs(gamma) > np.pi] = np.nan
          theta[np.abs(theta) > 2*np.pi] = np.nan
      else:
          gamma = np.nan if np.abs(gamma)>np.pi else gamma
          theta = np.nan if np.abs(theta)>2*np.pi else theta
      coluna0 = [q_exp(-theta,q).real*q_cos(gamma,q).real]
      coluna1 = [q_exp(theta,q).real*q_sin(gamma,q).real]
      return array([coluna0,coluna1])
  
  
  def q_phi(u,q=1,israd=True):
  	u = radian(u) if(not israd) else u
  	param_q = lambda q_: 1/(1-q_)
  	param_q = limit(param_q,q)
  	return param_q*atan((1-q)*u)
  
  
  def q_qubit(state):
      pass
  
  
  from IPython.display import Math,display
  class QuantumCircuit:
      def __init__(self,n_bits,q: float=1,israd=True)-> None:
          import numpy as np
          self.q = q
          self.theta = .5*np.pi
          self.israd = israd
          self.n_bits = n_bits
          circuit_painel = []
          for i in range(n_bits):
              circuit_painel.append(f'[q{i}]--')
          self.circuit_painel = circuit_painel
          q_qubits = []
          for i in range(n_bits):
              q_qubits.append(q_psi(gamma=0.5*np.pi,theta=1*np.pi,q=self.q))
          self.q_qubits = np.array(q_qubits)
          #print(self.q_qubits)
          self.divisor_circuits = False
          #creating a list for probabilistic
          self.probs_history = []
          
      
      def get_prob_from_qbits(self,qbits):
          #print(qbits)
          q_probs = []
          i = 0
          for qbit in qbits:
              qbit_prob = {}
              total_state = abs(qbit[0]) + abs(qbit[1])
              percent_zero_state = abs(qbit[0])/total_state
              percent_one_state = abs(qbit[1])/total_state
              
              #print(f'prob[{i}]: [{percent_zero_state},{percent_one_state}]')
              qbit_prob['alloc'] = i
              #print(f'[q{i}] {qbit}')
              qbit_prob['name_qbit'] = f'q{i}'
              qbit_prob['|0>'] = percent_zero_state
              qbit_prob['|1>'] = percent_one_state
              qbit_prob['state'] = '|0>' if qbit_prob['|0>']>=qbit_prob['|1>'] else '|1>'
              q_probs.append(qbit_prob)
              #print(q_probs[i])
              i+=1
          #print(f'qprobs: {q_probs}')
          return q_probs
  
              
          
          
      def R_x(self):
          theta = self.theta
          q = self.q
          israd = self.israd
          theta = theta if israd else radian(theta)
          theta = theta/2
          return np.array([[q_cos(u=theta,q=q,israd=israd),
                              q_sin(u=theta,q=q)*-1j],
                              [q_sin(theta,q=q,israd=israd)*-1j,
                              q_cos(u=theta,q=q,israd=israd)]
                              ])
  
  
      def R_y(self):
          theta = self.theta
          q = self.q
          israd = self.israd
          theta = theta if israd else radian(theta)
          theta = theta/2
          return np.array([[q_cos(u=theta,q=q,israd=israd),
                              q_sin(u=theta,q=q,israd=israd)*-1],
                              [q_sin(theta,q=q,israd=israd),
                              q_cos(u=theta,q=q,israd=israd)]
                              ]).real
  
  
      def R_z(self):
          theta = self.theta
          q = self.q
          israd = self.israd
          theta = theta if israd else radian(theta)
          theta = 1j*theta/2
          return np.array([[q_exp(u=-theta,q=q),0],
                              [0,q_exp(u=theta,q=q)]]).real
  
  
      def H(self,*n_bits):
          str_cir = '--[H]--'
          str_nan = '-------'
          circuit_painel = self.circuit_painel
          i = 0
          new_circuit = []
          for line in circuit_painel:
              if i in n_bits:
                  line = line+str_cir
                  new_circuit.append(line)
              else:
                  line=line+str_nan
                  new_circuit.append(line)
              i += 1
          self.circuit_painel = new_circuit
          self.qprobs = self.get_prob_from_qbits(self.q_qubits)
  
              
          theta = self.theta
          q = self.q
          israd = self.israd
          R_x = self.R_x
          R_y = self.R_y
          
          H_matriz = (R_x()+R_y()).real/np.sqrt(2)
          
          new_q_qubits = []
          q_qubits = self.q_qubits
          i = 0
          for qbit in q_qubits:
              if i in n_bits:
                  qbit = np.dot(H_matriz,qbit)
                  new_q_qubits.append(qbit)
              else:
                  qbit = qbit
                  new_q_qubits.append(qbit)
              i+=1
          self.q_qubits = new_q_qubits
          self.qprobs = self.get_prob_from_qbits(self.q_qubits)
  
      def cnot(self,bits):
          bit_alloc0,bit_alloc1 = bits
          str_cir_x = '---X---'
          str_cir_o = '---O---'
          str_cir_n = '-------'
          circuit_painel = self.circuit_painel
          i = 0
          new_circuit = []
          for line in circuit_painel:
              if i == bit_alloc0:
                  line = line + str_cir_x
                  new_circuit.append(line)
              elif i==bit_alloc1:
                  line = line+str_cir_o
                  new_circuit.append(line)
              else:
                  line = line + str_cir_n
                  new_circuit.append(line)
              i+=1
          qubits = self.q_qubits
          new_qubits = 0
          i=0
          result_vector = np.kron(qubits[bit_alloc0],qubits[bit_alloc1])
          cnot = [[1,0,0,0],
                  [0,1,0,0],
                  [0,0,0,1],
                  [0,0,1,0]]
          cnot = np.array(cnot)
          new_state = np.kron(result_vector,cnot)
          total = sum(new_state)
          probs = new_state/total
          return probs,new_state
                                 
          
      
      def X(self,*n_bits):
          str_cir = '--[X]--'
          str_nan = '-------'
          circuit_painel = self.circuit_painel
          i = 0
          new_circuit = []
          for line in circuit_painel:
              if i in n_bits:
                  line = line+str_cir
                  new_circuit.append(line)
              else:
                  line=line+str_nan
                  new_circuit.append(line)
              i += 1
          self.circuit_painel = new_circuit
              
          theta = self.theta
          q = self.q
          israd = self.israd
          R_x = self.R_x
          new_q_qubits = []
          q_qubits = self.q_qubits
          i = 0
          for qbit in q_qubits:
              if i in n_bits:
                  qbit = np.dot(R_x(),qbit)
                  new_q_qubits.append(qbit)
              else:
                  qbit = qbit
                  new_q_qubits.append(qbit)
              i+=1
          self.q_qubits = new_q_qubits
          self.qprobs = self.get_prob_from_qbits(self.q_qubits)
  
  
      def Y(self,*n_bits):
          str_cir = '--[Y]--'
          str_nan = '-------'
          circuit_painel = self.circuit_painel
          i = 0
          new_circuit = []
          for line in circuit_painel:
              if i in n_bits:
                  line = line+str_cir
                  new_circuit.append(line)
              else:
                  line=line+str_nan
                  new_circuit.append(line)
              i += 1
          self.circuit_painel = new_circuit
              
          theta = self.theta
          q = self.q
          israd = self.israd
          R_y = self.R_y
          new_q_qubits = []
          q_qubits = self.q_qubits
          i = 0
          for qbit in q_qubits:
              if i in n_bits:
                  qbit = np.dot(R_y(),qbit)
                  new_q_qubits.append(qbit)
              else:
                  qbit = qbit
                  new_q_qubits.append(qbit)
              i+=1
          self.q_qubits = new_q_qubits
          self.qprobs = self.get_prob_from_qbits(self.q_qubits)
  
      
      def Z(self,*n_bits):
          str_cir = '--[Z]--'
          str_nan = '-------'
          circuit_painel = self.circuit_painel
          i = 0
          new_circuit = []
          for line in circuit_painel:
              if i in n_bits:
                  line = line+str_cir
                  new_circuit.append(line)
              else:
                  line=line+str_nan
                  new_circuit.append(line)
              i += 1
          self.circuit_painel = new_circuit
              
          theta = self.theta
          q = self.q
          israd = self.israd
          R_z = self.R_z
          new_q_qubits = []
          q_qubits = self.q_qubits
          i = 0
          for qbit in q_qubits:
              if i in n_bits:
                  qbit = np.dot(R_z(),qbit)
                  new_q_qubits.append(qbit)
              else:
                  qbit = qbit
                  new_q_qubits.append(qbit)
              i+=1
          self.q_qubits = new_q_qubits
          self.qprobs = self.get_prob_from_qbits(self.q_qubits)
  
        
              
      def states(self) -> str:
          string = []
          i = 0
          for qbit in self.q_qubits:
              string.append(f'[q{i}]|ψ〉 = {round(qbit[0][0],3)}|0〉 + {round(qbit[1][0],3)}|1〉')
              i += 1
          string = '\n'.join(string)
          string = '\n'+string
          #print(display(Math(r'$\frac{df}{dx}$')))
          return string
      
      def med(self,*n_bits):
                 #getting the probability of the two states of qbit
          q_probs = []
          i = 0
          new_qubits = []
          for qbit in self.q_qubits:
              if i in n_bits:
                  qbit_prob = {}
                  total_state = qbit[0]+qbit[1]
                  percent_zero_state = qbit[0]/total_state
                  percent_one_state = qbit[1]/total_state
                  qbit_prob['alloc'] = i
                  qbit_prob['name_qbit'] = f'q{i}'
                  qbit_prob['|0>'] = percent_zero_state
                  qbit_prob['|1>'] = percent_one_state
                  qbit_prob['state'] = '|0>' if qbit_prob['|0>']>=qbit_prob['|1>'] else '|1>'
                  q_probs.append(qbit_prob)
                  if qbit_prob['state'] == '|1>':
                      qbit = [0,1]
                  else:
                      qbit = [1,0]
                  #print(f'[q{i}] {qbit}')
              else:
                  pass
              new_qubits.append(qbit)
              i+=1
          #print(i)
          str_cir = '-[med]-'
          str_nan = '-----------'
          circuit_painel = self.circuit_painel
          i = 0
          c=0
          new_circuit = []
          for line in circuit_painel:
              if i in n_bits:
                  #print(q_probs)
                  n_simb = q_probs[c]['state']
                  line = line+str_cir+f'{n_simb}-'
                  new_circuit.append(line)
                  c+=1
              else:
                  line=line+str_nan
                  new_circuit.append(line)
              i += 1
          self.circuit_painel = new_circuit
          self.q_probs = q_probs
          self.q_qubits = new_qubits
          self.qprobs = self.get_prob_from_qbits(self.q_qubits)
  
          return q_probs
  
      def med_all(self):
          n_bits = [nq for nq in range(len(self.q_qubits))]
  
          #getting the probability of the two states of qbit
          q_probs = []
          i = 0
          new_qubits = []
          qbits = [0,0]
          for qbit in self.q_qubits:
              #print('after',qbit)
              if i in n_bits:
                  qbit_prob = {}
                  total_state = qbit[0]+qbit[1]
                  percent_zero_state = qbit[0]/total_state
                  percent_one_state = qbit[1]/total_state
                  #print('percent_one',percent_one_state)
                  #print('percent_zero',percent_zero_state)
                  qbit_prob['alloc'] = i
                  qbit_prob['name_qbit'] = f'q{i}'
                  qbit_prob['|0>'] = percent_zero_state
                  qbit_prob['|1>'] = percent_one_state
                  qbit_prob['state'] = '|0>' if qbit_prob['|0>']>=qbit_prob['|1>'] else '|1>'
                  q_probs.append(qbit_prob)
                  #print(qbit_prob['state'])
                  #print(1 if qbit_prob['state']=='|1>' else 0)
                  if qbit_prob['state'] == '|1>':
                      qbit = [0,1]
                  else:
                      qbit = [1,0]
                  #print(f'[q{i}] {qbit}')
              else:
                  pass
              new_qubits.append(qbit)
              #print(new_qubits)
              i+=1
          #print(i)
          str_cir = '-[med]-'
          str_nan = '-----------'
          circuit_painel = self.circuit_painel
          i = 0
          c=0
          new_circuit = []
          for line in circuit_painel:
              if i in n_bits:
                  #print(q_probs)
                  n_simb = q_probs[c]['state']
                  line = line+str_cir+f'{n_simb}-'
                  new_circuit.append(line)
                  c+=1
              else:
                  line=line+str_nan
                  new_circuit.append(line)
              i += 1
          self.circuit_painel = new_circuit
          self.q_probs = q_probs
          self.q_qubits = new_qubits
          #print(self.q_qubits)
          self.qprobs = self.get_prob_from_qbits(self.q_qubits)
  
          return q_probs
      
      def __str__(self):
          print_circuit = self.circuit_painel
          if self.divisor_circuits:
              print_circuit.append(self.divisor_circuits)
              print_circuit.append('\n'.join(self.classic_circuit_painel))
          #print(print_circuit)
          #self.circuit_painel = []
          return '\n'.join(print_circuit)
      
      
      
  class q_qubitq:
      def __init__(self,*bit_values: int) -> None:
          self.values = list(bit_values)
  # \na^2 + b^2 = {qbit[0][0]**2+qbit[1][0]**2}')
  
  