Usage Directions - Core SDK

Obtaining the backend for execution

Obtain your account name and token from the Quantum Rings team. You can then use them to create the backend for execution. You can follow the reference code below for further information:

import QuantumRingsLib
from QuantumRingsLib import QuantumRegister, AncillaRegister, ClassicalRegister, QuantumCircuit
from QuantumRingsLib import QuantumRingsProvider
from QuantumRingsLib import job_monitor
from QuantumRingsLib import JobStatus
from matplotlib import pyplot as plt
import numpy as np

provider = QuantumRingsProvider(token =<YOUR_TOKEN_HERE>, name=<YOUR_ACCOUNT_NAME_HERE>)
backend = provider.get_backend("scarlet_quantum_rings")
shots = 100

provider.active_account()

Executing the Code

You can execute the code in the backend illustrated below and setup a job monitor to watch for completion. The code is executed in a background thread so you can use your system normally.

Using job_monitor function

job = backend.run(qc, shots)
job_monitor(job)
result = job.result()
counts = result.get_counts()
print (counts)

Using wait_for_final_state function

def jobCallback(job_id, state, job):
    #print("Job Status: ", state)
    pass

# Execute the circuit
job = backend.run(qc, shots)
job.wait_for_final_state(0, 5, jobCallback)
counts = job.result().get_counts()

Importing QASM2 code

For importing QASM2 code, refer to the following code example.

Importing QASM2 code

from QuantumRingsLib import QuantumCircuit
qc = QuantumCircuit.from_qasm_file("C:\\Users\\vkasi\\Desktop\\Sycamore\\1\\circuit_n12_m14_s0_e0_pEFGH.qasm")

Using c_if control statements

c_if control is supported on all gates. The following code fragment illustrates this concept.

c_if control

shots = 1
numberofqubits = 4
q = QuantumRegister(numberofqubits , 'q')
c = ClassicalRegister(numberofqubits , 'c')
qc = QuantumCircuit(q, c)

qc.x([q[0],q[1]])
qc.measure(0, 0)
qc.measure(1, 1)
qc.reset(0)
qc.reset(1)
qc.x(q[1]).c_if(c[0],1)
qc.x(q[2]).c_if(c[1],1)
qc.measure_all()

# Execute the quantum code
job = backend.run(qc, shots)
job_monitor(job)


result = job.result()
counts = result.get_counts()
print(counts)

The equivalent QASM2 code can be generated using the instruction outlined in the following section.

Generating equivalent QASM2 code

qc.qasm(True)

The output of the above instruction is:

OPENQASM 2.0;
include "qelib1.inc";

// Register definitions
qreg q[4];
creg c[4];

// Instructions
x q[0];
x q[1];
measure q[0] -> c[0];
measure q[1] -> c[1];
reset q[0];
reset q[1];
if (c[0]  == 1) x q[1];
if (c[1]  == 1) x q[2];
measure q[0] -> c[0];
measure q[1] -> c[1];
measure q[2] -> c[2];
measure q[3] -> c[3];

The generated QASM2 code can be imported in other systems and executed.

Creating Parameterized Quantum Circuits

Parameterized quantum circuits (PQCs) are handy in creating reusable circuit components. PQCs are typically used in variational circuits and in creating machine learning algorithms. Using this method, arguments to circuit instructions can be defined using symbols. They can later be assigned actual values before executing. The following code snippet illustrates this concept.

** create the parameters **

# import the required modules
import QuantumRingsLib
from QuantumRingsLib import QuantumRegister, AncillaRegister, ClassicalRegister, QuantumCircuit
from QuantumRingsLib import QuantumRingsProvider
from QuantumRingsLib import Parameter, ParameterVector
import math

# create the quantum circuit
total_qubits = 5
q = QuantumRegister (total_qubits, "q")
c = ClassicalRegister (total_qubits, "c")
qc = QuantumCircuit (q, c)

# create the parameters
myparamvec = ParameterVector("test", 6)
theta = Parameter("theta")
phi = Parameter("phi")
lam = Parameter("lambda")
gamma = Parameter("gamma")

#build the quantum circuit
qc.h(q[0]);
qc.x(q[1]);
qc.x(q[2]);
qc.h(q[3]);
qc.x(q[4]);
qc.h(q[4]);
qc.mcp(theta, [q[0],q[1],q[3]], q[2]);
qc.rx(phi, 3 )
qc.ry(math.pi/2, 4 )
qc.rz(myparamvec[5], 0 )
qc.u(myparamvec[0], myparamvec[1], myparamvec[2], 1)

Draw the parameterized circuit

qc.draw()

Executing the above code produces the following figure:

       ┌───┐                  ┌──────────────┐
q[0]: ■┤ H ├───────────■──────┤ RZ(test[5])  ├─────────────────
       ├───┤           │      ├──────────────┴───────────────┐
q[1]: ■┤ X ├───────────■──────┤ U(test[0], test[1], test[2]) ├
       ├───┤     ┌─────┴─────┐└──────────────────────────────┘
q[2]: ■┤ X ├─────┤ P(theta)  ├────────────────────────────────
       ├───┤     └─────┬─────┘┌──────────┐
q[3]: ■┤ H ├───────────■──────┤ RX(phi)  ├─────────────────────
       ├───┤┌───┐             ├──────────┤
q[4]: ■┤ X ├┤ H ├─────────────┤ RY(pi/2) ├─────────────────────
       └───┘└───┘             └──────────┘
c: 5/ ■═══════════════════════════════════════════════════════

Assigning parameters to the circuit

The following code illustrates how to assign parameters to the quantum circuit.

# create the parameters
myparam = {"test[0]": math.pi,
           "test[1]": math.pi/2,
           "test[2]": math.pi/3,
           "test[3]": math.pi/4,
           "test[4]": math.pi/6,
           "test[5]": math.pi/7,
           "theta"  : math.pi/8,
           "phi"    : math.pi/9,
           "lambda" : math.pi/11,
           "gamma"  : math.pi/13,
          }

# assign the parameters to the circuit
qc.assign_parameters(myparam, inplace=True)
qc.draw()

Executing the above code produces the following figure:

       ┌───┐                ┌──────────┐
q[0]: ■┤ H ├──────────■─────┤ RZ(pi/7) ├───────────
       ├───┤          │     ├──────────┴─────────┐
q[1]: ■┤ X ├──────────■─────┤ U(pi, pi/2, pi/3)  ├
       ├───┤     ┌────┴────┐└────────────────────┘
q[2]: ■┤ X ├─────┤ P(pi/8) ├──────────────────────
       ├───┤     └────┬────┘┌──────────┐
q[3]: ■┤ H ├──────────■─────┤ RX(pi/9) ├───────────
       ├───┤┌───┐           ├──────────┤
q[4]: ■┤ X ├┤ H ├───────────┤ RY(pi/2) ├───────────
       └───┘└───┘           └──────────┘
c: 5/ ■═══════════════════════════════════════════

We see that the parameters are assigned. This circuit can be executed using the methods outlined in previous sections.

Usage Directions - toolkit for qiskit

The toolkit for qiskit provides a unique way for developers and researchers to combine the versatility of qiskit and the power of the Quantum Rings SDK to develop complex circuits rapidly, develop and test them locally, on the cloud, or in the supercomputing facility before deploying them in real quantum hardware. This process could save precious time and money.

The following code sample illustrates how to develop the quantum circuit using qiskit and execute it on the Quantum Rings Backend.

Step 1. Import the required modules

# Import the required modules
from qiskit.circuit import QuantumCircuit
from qiskit import QuantumCircuit, transpile, QuantumRegister, ClassicalRegister, AncillaRegister
from qiskit.visualization import plot_histogram
from matplotlib import pyplot as plt

import QuantumRingsLib
from QuantumRingsLib import QuantumRingsProvider
from quantumrings.toolkit.qiskit import QrBackendV2
from quantumrings.toolkit.qiskit import QrJobV1

from matplotlib import pyplot as plt

# Acquire the Quantum Rings Provider
qr_provider = QuantumRingsProvider(token =<YOUR_TOKEN_HERE>, name=<YOUR_ACCOUNT_NAME_HERE>)

Step 2. Build the Quantum Circuit

shots = 1000
numberofqubits =  int(qr_provider.active_account()["max_qubits"])
q = QuantumRegister(numberofqubits , 'q')
c = ClassicalRegister(numberofqubits , 'c')
qc = QuantumCircuit(q, c)


# Create the GHZ state (Greenberger–Horne–Zeilinger)
qc.h(0);
for i in range (qc.num_qubits - 1):
    qc.cx(i, i + 1);

# Measure all qubits
qc.measure_all();

Step 3. Execute the Quantum Circuit using the Quantum Rings Backend

Note

You have to transpile the qiskit constructed Quantum Circuit before execution. If you know the number of qubits your circuit uses, you can provide it as an argument to the QrBackendV2 class constructor. Including this parameter in the constructor often helps in efficient transpilation. Also, it is a good idea to set the initial_layout for the transpiler.

# Execute the quantum code
mybackend = QrBackendV2(qr_provider, num_qubits = qc.num_qubits)
qc_transpiled = transpile(qc, mybackend, initial_layout=[i for i in range(0, qc.num_qubits)])
job = mybackend.run(qc_transpiled, shots = shots)

result = job.result()
counts = result.get_counts()
plot_histogram(counts)

You must see a nearly equal distribution of the 0 and 1 states.

Using the Sampler

Using the Sampler is an efficient way to sample the bitstrings. Qiskit Ecosystem components, such as the Finance toolkit, use Sampler and Estimator modules to efficiently execute quantum circuits.

Note

Before using the Sampler or Estimator, we recommend locally saving your Quantum Rings account. If the account is saved locally, only a few changes to the qiskit tutorials are required to get them running.

Saving the Quantum Rings account locally

First, obtain the Quantum Rings Provider and then use the QuantumRingsLib.QuantumRingsProvider.save_account() API to save your account locally. You can use the following code example.

import QuantumRingsLib
from QuantumRingsLib import QuantumRingsProvider

#Acquire the Quantum Rings Provider
provider = QuantumRingsProvider(token =<YOUR_TOKEN_HERE>, name=<YOUR_ACCOUNT_NAME_HERE>)
print("Account Name: ", provider.active_account()["name"], "\nMax Qubits: ", provider.active_account()["max_qubits"])

#Save the account locally.
provider.save_account(token =<YOUR_TOKEN_HERE>, name=<YOUR_ACCOUNT_NAME_HERE>)
print(provider.saved_accounts(False, "default"))

Sampler: Example Code

Now, you can try the following code, which uses QrSamplerV1.

from  quantumrings.toolkit.qiskit import QrSamplerV1 as Sampler
from qiskit import QuantumCircuit
from qiskit.circuit.library import RealAmplitudes

# a Bell circuit
bell = QuantumCircuit(2)
bell.h(0)
bell.cx(0, 1)
bell.measure_all()

# two parameterized circuits
pqc = RealAmplitudes(num_qubits=2, reps=2)
pqc.measure_all()
pqc2 = RealAmplitudes(num_qubits=2, reps=3)
pqc2.measure_all()

theta1 = [0, 1, 1, 2, 3, 5]
theta2 = [0, 1, 2, 3, 4, 5, 6, 7]

# initialization of the sampler
sampler = Sampler()

# Sampler runs a job on the Bell circuit
job = sampler.run(circuits=[bell], parameter_values=[[]], parameters=[[]])
job_result = job.result()
print([q.binary_probabilities() for q in job_result.quasi_dists])

# Sampler runs a job on the parameterized circuits
job2 = sampler.run(
    circuits=[pqc, pqc2],
    parameter_values=[theta1, theta2],
    parameters=[pqc.parameters, pqc2.parameters])
job_result = job2.result()
print([q.binary_probabilities() for q in job_result.quasi_dists])

Note

To switch to IBM runtime, you could simply change the importer for the Sampler as follows:

from qiskit.primitives import Sampler

The rest of the code remains the same!

Using the Estimator

Using the estimator is straightforward. We illustrate that from the CHSH inequality example from the qiskit tutorials:

Note

Currently, qiskit uses two variants of the Sampler and Estimator modules, V1 and V2, with support for V1 expiring shortly. Some qiskit packages, such as the finance toolkit, currently use V1 Sampler and Estimator modules. Please check which one you need and import the correct module.