# Подключаем необходимые методы/классы/etc.
from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister
from qiskit import execute

# Указываем необходимое число квантовых регистров (кубитов)
# и классических регистров.
# 
# В обоих случаях указано 2. Помимо количества им присваиваются идентификаторы
# в рамках программы `qp`: "qr" и "cr" соответственно.
qr = QuantumRegister(2, name="qr")
cr = ClassicalRegister(2, name="cr")

# Создаём квантовую цепь с квантовыми регистрами `qr`, классическими регистрами `cr` и называем её "Bell".
qc = QuantumCircuit(qr, cr, name="Bell")

# Начинаем добавлять различные гейты (операции над кубитами).
# 
# Синтаксис следующий:
# "<квантовая_цепь>.<гейт>(<квантовый/классический регистр, параметры, etc. - в зависимости от гейта>)".

# Гейт Адамара на нулевой кубит.
qc.h(qr[0])
# Controlled NOT (CNOT) гейт, который использует `qr[0]` кубит как управляющий, а кубит `qr[1]` как таргет.
qc.cx(qr[0], qr[1])
# Измерить кубиты и записать измеренные значения в классические регистры.
qc.measure(qr, cr)

# Скомпилировать и запустить выполнение программы
# "Bell" на бэкэнде "local_qasm_simulator".
job_sim = execute(qc, "local_qasm_simulator")
result = job_sim.result()

# Посмотреть на результаты программы в виде "{'00': n1, '01': n2, ...}", где n1, n2, ... - число
# соответствующих исходов.
print(result.get_counts("Bell"))

from qiskit.tools.visualization import circuit_drawer

# Изобразим квантовую цепь, которая соответствует "Bell".
circuit_drawer(qc)

from qiskit.tools.visualization import plot_histogram

# Гистограмма распределения исходов.
plot_histogram(result.get_counts('Bell'))

# Посмотреть на программу в представлении OpenQASM - Open Quantum Assembly Language. Этот язык используется
# для описания инструкций к квантовой машине.
print(qc.qasm())

from qiskit import register
import Qconfig

# Указываем APItoken и ссылку на соответствующий API.
qx_config = {
        "APItoken": Qconfig.APItoken,
        "url": Qconfig.config['url']
}
print('Qconfig loaded from %s.' % Qconfig.__file__)

# Регистрируемся в системе IBM Q.
register(qx_config['APItoken'], qx_config['url'])

from qiskit import available_backends

# Список облачных устройств, которые подключены к сети.
available_backends({'local': False})

from qiskit import get_backend

# По названию устройства можно проверить его статус.
get_backend('ibmqx5').status

# Следующими строками можно найти доступный в настоящий момент времени квантовое устройство, которое имеет 16 кубит.
#
# Составляем список из всех облачных бэкэндов (симуляторы не рассматриваем, потому что у них нет параметра 'n_qubits').
backends = available_backends({'local': False})

real_device_backend = [backend for backend in backends if
                       get_backend(backend).configuration['n_qubits'] == 16 and
                       get_backend(backend).status['available'] == True]
real_device_backend

backend = real_device_backend[0]

# Список из квантовых схем, которые хочется запустить на квантовом устройстве. Здесь
# может быть перечислено несколько схем, это будет оптимальнее запускать на сервере
# в том плане, что на сервере есть очередь на запуск.
circuits = [qc]

# Количество "запусков" программы на бэкэнде. Максимальное число равно 8192.
shots = 1024
# Максимальное число кредитов, которые вы готовы потратить за запуск программы.
max_credits = 5

# Отпраляем программу в облако и ожидаем выполнения.

job = execute(circuits, backend=backend, shots=shots,
              max_credits=max_credits)
result_real = job.result()

# Проверяем статус программы.
result_real.get_status()

%%time
# Гистограмма распределения исходов на настоящем квантовом устройстве.
plot_histogram(result_real.get_counts('Bell'))

qc_1 = qp.create_circuit('qc_1', [qr], [cr])
qc_1.measure(qr[0], cr[0])

# Теперь заметим, что квантовая цепь "qc_1" добавилась к остальным.
qp.get_circuit_names()

# Запустим на локальном симуляторе и построим гистограмму исходов.
result = qp.execute('qc_1')
plot_histogram(result.get_counts('qc_1'))

%%time
# Запустим ту же цепь на реальном устройстве.
result_real = qp.execute('qc_1', backend=backend,
                         shots=shots, max_credits=max_credits,
                         wait=wait_time, timeout=timeout)
plot_histogram(result_real.get_counts('qc_1'))

qc_2 = qp.create_circuit('qc_2', [qr], [cr])
# Добавляем NOT-гейт.
qc_2.x(qr[0])
qc_2.measure(qr[0], cr[0])

result = qp.execute('qc_2')
plot_histogram(result.get_counts('qc_2'))

qc_3 = qp.create_circuit('qc_3', [qr], [cr])
# Добавляем гейт Адамара.
qc_3.h(qr[0])
qc_3.measure(qr[0], cr[0])

result = qp.execute('qc_3')
plot_histogram(result.get_counts('qc_3'))

aux = qp.create_circuit('auxiliary', [qr], [cr])
aux.cx(qr[0], qr[1])

# Измерения.
aux.measure(qr[0], cr[0])
aux.measure(qr[1], cr[1])

utils.circuitImage(aux, utils.basis)

qc_4 = qp.create_circuit('qc_4_begin', [qr], [cr])
qc_4.x(qr[0])
qc_4.x(qr[1])

# Квантовые цепи можно "складывать" вместе, вернее, составлять.
qc_4 = qc_4 + aux

# Таким образом мы получаем четвертую квантовую цепь.
utils.circuitImage(qc_4, utils.basis)

# И записываем её в программу под соответствующим названием.
qp.add_circuit('qc_4', qc_4)

result = qp.execute('qc_4')
plot_histogram(result.get_counts('qc_4'))

qc_5 = qp.create_circuit('qc_5_begin', [qr], [cr])
qc_5.h(qr[0])
qc_5.x(qr[1])

qc_5 = qc_5 + aux
qp.add_circuit('qc_5', qc_5)

utils.circuitImage(qp.get_circuit('qc_5'), utils.basis)

result = qp.execute('qc_5')
plot_histogram(result.get_counts('qc_5'))