# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3 (ipykernel)
#     language: python
#     name: python3
# ---

# # Parallelizing Remote QNode Execution
#
# This notebook demonstrates the importance of parallelizing the evaluation of cost functions gradients when using remote hardware.
# For this demonstration, we use open-access IBM quantum hardware simulators and qNetVO's parallelization functionality built on dask.
# These simulators have relatively short queue-times in comparison to the available quantum computers yet reveal the advantages of parallelizing the web requests used to invoke the remote IBM Q services.

# +
import pennylane as qml
from pennylane import numpy as np

import qnetvo as qnet

# +
from qiskit import IBMQ

# For details regarding integration between PennyLane and IMB Quantum,
# see https://pennylaneqiskit.readthedocs.io/en/latest/devices/ibmq.html#accounts-and-tokens
provider = IBMQ.load_account()
# -

# ## Setup
#
# For simplicity, we consider a CHSH scenario ansatz with a static Bell state preparation and local qubit measurements optimized over the $xz$-plane.
# The qnodes are executed/trained remotely on the `ibmq_qasm_simulator`.
# In certain cases, qnode execution is performed locally for greater efficiency.

# +
prep_nodes = [
    qnet.PrepareNode(1, [0,1], qnet.ghz_state, 0)
]
meas_nodes = [
    qnet.MeasureNode(2, 2, [0], qnet.local_RY, 1),
    qnet.MeasureNode(2, 2, [1], qnet.local_RY, 1)
]

dev_ibm_qasm = {
    "name" : "qiskit.ibmq",
    "shots" : 2000,
    "backend" : "ibmq_qasm_simulator",
    "provider" : provider
}

local_sim_chsh_ansatz = qnet.NetworkAnsatz(prep_nodes, meas_nodes)
ibm_sim_chsh_ansatz = qnet.NetworkAnsatz(
    prep_nodes, meas_nodes, dev_kwargs = dev_ibm_qasm
)
# -

# ## QNode Execution Parallelization
#
# We now demonstrate the performance gains granted by parallelizing qnode execution across IBM Q hardware simulator devices.
# In this example the CHSH cost function requires 4 remote qnode executions which are first run serially and then, in parallel.
#
# ### Non-Parallelized Remote QNode Execution
#
# By default, PennyLane will chain web requests invoking remote qnode execution serially. 
# This is a great ineffeciency granted that each of these qnode executions are independent from each other.

# +
# %%time

chsh_cost = qnet.chsh_inequality_cost(ibm_sim_chsh_ansatz)

np.random.seed(13)
rand_settings = ibm_sim_chsh_ansatz.rand_scenario_settings()

chsh_cost(rand_settings)
# -

# ### Parallelized Remote QNode Execution
#
# A factor of roughly 2-4x speedup is found by parallelizing the qnode execution across 4 separate threads.
# This speedup persists even when a single remote device runs all qnode executions because the parallelized web requests populate the queue sooner than serial web requests.
#
#

# +
# %%time

parallel_chsh_cost = qnet.chsh_inequality_cost(
    ibm_sim_chsh_ansatz, parallel=True
)

np.random.seed(13)
rand_settings = ibm_sim_chsh_ansatz.rand_scenario_settings()

parallel_chsh_cost(rand_settings)
# -

# ## QNode Gradients on Remote Hardware
#
# In this section we demonstrate that performance gains granted by parallelizing gradient computation of remote qnodes.
# The gradient is evaluated using the parameter shift rule.
#
#
# ### Non-Parallelized Remote Gradient Computation
#
# By default, all cost functions and gradients are evaluated serially on remote hardware.
# This requires 28 web requests in total to IBM remote simulator.

# +
# %%time

ibm_sim_chsh_cost = qnet.chsh_inequality_cost(ibm_sim_chsh_ansatz)

np.random.seed(13)
rand_settings = ibm_sim_chsh_ansatz.rand_scenario_settings()

qnet.gradient_descent(
    ibm_sim_chsh_cost,
    rand_settings,
    num_steps=1,
    sample_width=1,
    step_size=0.1
)
# -

# ### Parallelized Remote Gradient Computation
#
# To speed up the gradient computation, we parallelize the parameter shift rule across 4 web requests.
# In total only 20 web requests to remote IBM hardware are needed where we are able to drop 8 requests from the previous example by evaluating the cost function locally.
# The training is then isolated in 20 web requests split across four independent threads.
# We see a rough 4x improvement in training time.

# +
# %%time

local_sim_chsh_cost = qnet.chsh_inequality_cost(local_sim_chsh_ansatz)
parallel_grad_fn = qnet.parallel_chsh_grad(
    ibm_sim_chsh_ansatz, diff_method="parameter-shift"
)


np.random.seed(13)
rand_settings = ibm_sim_chsh_ansatz.rand_scenario_settings()

qnet.gradient_descent(
    local_sim_chsh_cost,
    rand_settings,
    num_steps=1,
    sample_width=1,
    step_size=0.1,
    grad_fn=parallel_grad_fn
)
# -



StarcoderdataJupyterScriptsDedupFiltered