## import essential libraries
import numpy as np

from qiskit import *
from qiskit.tools.monitor import job_monitor

from qiskit import assemble,pulse,QuantumCircuit,schedule,transpile
from qiskit.pulse.channels import ControlChannel, DriveChannel

from qiskit.circuit import Gate
from qiskit.providers.aer import PulseSimulator
from qiskit.providers.aer.pulse import PulseSystemModel
from qiskit.test.mock import FakeValencia
from qiskit.visualization import plot_histogram

from qiskit.visualization.pulse_v2 import draw, IQXSimple, IQXDebugging,IQXStandard


IBMQ.load_account()
provider = IBMQ.get_provider(group='open')

#check open_pulse ibmq device that you can use
provider.backends(simulator=False, open_pulse=True)

#define pulsesimulator from the backend profile

backend = FakeValencia()

# setting for build pulse simulation
model = PulseSystemModel.from_backend(backend)
qubit_lo_freq = model.hamiltonian.get_qubit_lo_from_drift()
backend_sim = PulseSimulator(system_model = model)
backend_sim.set_options(qubit_lo_freq=backend.defaults().qubit_freq_est)


qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0,1)
qc.measure_all()

qc.draw('mpl')


qc_t = transpile(qc, backend)
pulse_sched_qc = schedule(qc_t, backend)
draw(pulse_sched_qc, backend=backend)

from qiskit import pulse

dc = pulse.DriveChannel
d0, d1, d2, d3, d4 = dc(0), dc(1), dc(2), dc(3), dc(4)

with pulse.build(name='QHK2022_Pulse') as QHK2022:
    pulse.play([1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1 ], d0)
    pulse.play([1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1 ], d1)
    pulse.play([1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0 ], d2)
    pulse.play([1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0 ], d3)
    pulse.play([1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1], d4)


draw(QHK2022)

with pulse.build(backend) as drive_sched_check:
    for i in range(5):
        print("The driveing channel of qubit{} is {}".format(i, pulse.drive_channel(i)))

from qiskit.tools.jupyter import *
backend

#check the control channel btw 0-1
with pulse.build(backend) as control_sched_check:
    print("The control channel of qubit btw qubit (0,1) is {}".format(0, pulse.control_channels(0,1)))

with pulse.build(backend) as control_sched_check:
    print("The control channel of qubit btw qubit (0,2) is {}".format(0, pulse.control_channels(0,2)))

with pulse.build(backend) as drive_sched_check:
    for i in range(5):
        print("The acquire channel of qubit{} is {}".format(i, pulse.acquire_channel(i)))
        print("The measurement channel of qubit{} is {}".format(i, pulse.measure_channel(i)))

with pulse.build(backend) as actual_playing:
    d0 = pulse.drive_channel(0)

    a0 = pulse.acquire_channel(0)

    #play square constant pulse at drive channel 0 with the duration 10 (computational duration) amplitude 1.0
    pulse.play(pulse.library.Constant(64, 1.0), d0)

    #give 20 steps of delay to the d0
    pulse.delay(20, d0)
    #give pi/2 phase shift to the d0 
    pulse.shift_phase(2, d0)
    pulse.play(pulse.library.Constant(64, 1.0), d0)

    #set phase of d0 as pi
    pulse.set_phase(0.5, d0)
    pulse.play(pulse.library.Constant(64, 1), d0)
    
    #define inner pulse schedule inside of the pulse builder
    with pulse.build() as inner_sched:
        #play a gaussian pulse with duration 20, amplitude 1, sigma 3.0 to d0
        pulse.play(pulse.library.Gaussian(64, 0.2, 3.0), d0)
    #play inner schedule at the main schedule
    pulse.call(inner_sched)

draw(actual_playing, backend = backend)

with pulse.build(backend) as actual_playing:

#with pulse.build(backend, default_alignment='left') as actual_playing:
#with pulse.build(backend, default_alignment='right') as actual_playing:
#with pulse.build(backend, default_alignment='sequential') as actual_playing_two:


    d0 = pulse.drive_channel(0)
    d1 = pulse.drive_channel(1)
    a0 = pulse.acquire_channel(0)

    #play square constant pulse at drive channel 0 with the duration 10 (computational duration) amplitude 1.0
    pulse.play(pulse.library.Constant(64, 1.0), d0)
    pulse.play(pulse.library.Constant(64, 1.0), d1)


    #give 20 steps of delay to the d0
    pulse.delay(20, d0)
    #give pi/2 phase shift to the d0 
    pulse.shift_phase(2, d0)
    pulse.play(pulse.library.Constant(10, 1.0), d0)

    #set phase of d0 as pi
    pulse.set_phase(0.5, d0)
    pulse.play(pulse.library.Constant(64, 1.0), d0)
    
    #define inner pulse schedule inside of the pulse builder
    with pulse.build() as inner_sched:
        #play a gaussian pulse with duration 20, amplitude 1, sigma 3.0 to d0
        pulse.play(pulse.library.Gaussian(64, 1.0, 3.0), d0)
    #play inner schedule at the main schedule
    pulse.call(inner_sched)

draw(actual_playing_two, backend = backend)

with pulse.build(backend, default_alignment='sequential') as meas:
    pulse.call(actual_playing)
    
    with pulse.align_left():
        pulse.measure(0)

draw(meas, style=IQXDebugging(), backend = backend)

result = backend.run(meas, shots=8192).result()

result.get_counts()


with pulse.build(backend, default_alignment='sequential') as entangle_circ:
    pulse.u2(0, np.pi, 0)
    pulse.cx(0,1)
    
    with pulse.align_left():
        pulse.measure(0)
        pulse.measure(1)
    
draw(entangle_circ, style=IQXDebugging(), backend=backend)


with pulse.build(backend, default_alignment='sequential') as example1:

    d0 = pulse.drive_channel(0)
    #play square constant pulse at drive channel 0 with the duration 200 (computational duration) amplitude 0.5
    pulse.play(pulse.library.Constant(200, 0.5), d0)



draw(example1, backend = backend)

with pulse.build(backend, default_alignment='sequential') as example2:

    d0 = pulse.drive_channel(0)
    #play square constant pulse at drive channel 0 with the duration 189 (computational duration) amplitude 0.5
    pulse.play(pulse.library.Constant(189, 0.5), d0)



draw(example2, backend = backend)

with pulse.build(backend, default_alignment='sequential') as example3:

    d0 = pulse.drive_channel(0)
    #play gaussian pulse at drive channel 0 with the duration 160 (computational duration) amplitude 0.2 with sigma=40
    pulse.play(pulse.library.Gaussian(duration=160, amp=(0.2), sigma=40), d0)



draw(example3, backend = backend)

with pulse.build(backend, default_alignment='sequential') as example4:

    d0 = pulse.drive_channel(0)
    #play gaussian pulse at drive channel 0 with the duration 160 (computational duration) amplitude 0.4 with sigma=40
    pulse.play(pulse.library.Gaussian(duration=160, amp=(0.5), sigma=40), d0)



draw(example4, backend = backend)

with pulse.build(backend, default_alignment='sequential') as measure:
    
    a0 = pulse.acquire_channel(0)
    m0 = pulse.MeasureChannel(0)

#commenting out and insert pulse schedule you want to play ex:pulse.call(test_pulse1)
    
    #pulse.call()
    
    pulse.acquire(500, m0, pulse.MemorySlot(0))

result = backend.run(measure, shots=8192).result()
result.get_counts()

