Using the 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 as 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=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=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.

from QuantumRingsLib import QuantumCircuit
qc = QuantumCircuit.from_qasm_file("C:\\Sycamore\\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.

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=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.

# 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.

Using the 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

Option 1. Using QuantumRingsProvider

# Acquire the Quantum Rings Provider
qr_provider = QuantumRingsProvider( token =<YOUR_TOKEN_HERE>, name=<YOUR_ACCOUNT_NAME_HERE> )
mybackend = QrBackendV2(qr_provider, num_qubits = qc.num_qubits)

Option 2. Using QrRuntimeService

from quantumrings.toolkit.qiskit import QrRuntimeService
service = QrRuntimeService( token =<YOUR_TOKEN_HERE>, name=<YOUR_ACCOUNT_NAME_HERE> )
mybackend = service.backend(name = "scarlet_quantum_rings", num_qubits = qc.num_qubits)

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

# Execute the quantum code

job = mybackend.run(qc, shots = shots)

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

You should observe a nearly equal distribution of the 0 and 1 states.

Using the Sampler

Using the Sampler is an efficient way to develop the quantum circuit using qiskit and sample the bitstrings using the Quantum Rings SDK. Qiskit Ecosystem components, such as the Finance toolkit, use Sampler and Estimator modules to efficiently execute quantum circuits.

Sampler: Example Code

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

import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

from quantumrings.toolkit.qiskit import QrRuntimeService
from quantumrings.toolkit.qiskit import QrSamplerV2 as Sampler

service = QrRuntimeService( token =<YOUR_TOKEN_HERE>, name=<YOUR_ACCOUNT_NAME_HERE> )

n_qubits = 12

backend = service.backend(name = "scarlet_quantum_rings", precision = "single", gpu = 0, num_qubits = n_qubits)


# Create a Quantum Circuit
mat = np.real(random_hermitian(n_qubits, seed=1234))
circuit = IQP(mat)
circuit.measure_all()

# Put it through the pass manager
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)

# Now execute the circuit using Quantum Rings toolkit
sampler = Sampler(backend = backend)
job = sampler.run([isa_circuit])
result = job.result()

pub_result = result[0]

print(f" > First ten results: {pub_result.data.meas.get_bitstrings()[:10]}")

You can download this example and learn advanced methods of using the sampler in this tutorial: Sampler V2 Sample Programs

Using the Estimator

The following example illustrates the usage of the estimator class, which helps measure Pauli expectation values.

from dataclasses import asdict
from quantumrings.toolkit.qiskit import QrRuntimeService

service = QrRuntimeService( token = <YOUR_TOKEN_HERE>, name = <YOUR_ACCOUNT_NAME_HERE> )
available_backends = service.backends()
print(available_backends)
backend = service.backend(name = "scarlet_quantum_rings", precision = "double", gpu = 0, num_qubits = 12)

from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp

# Step 1: Define operator
op = SparsePauliOp.from_list(
    [
        ("II", -1.052373245772859),
        ("IZ", 0.39793742484318045),
        ("ZI", -0.39793742484318045),
        ("ZZ", -0.01128010425623538),
        ("XX", 0.18093119978423156),
    ]
)

# Step 2: Define quantum state
circuit = QuantumCircuit(2)
circuit.x(0)
circuit.x(1)

from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)

isa_observable = op.apply_layout(isa_circuit.layout)

from quantumrings.toolkit.qiskit import QrEstimatorV2 as Estimator
estimator = Estimator(backend = backend)
job = estimator.run([(isa_circuit, isa_observable)])

# Get results for the first (and only) PUB
pub_result = job.result()[0]

print(f">>> Expectation value: {pub_result.data.evs[0]}")

You can download this example and learn advanced methods of using the estimator class in this tutorial: EstimatorV2 Usage

Note

We recommand you to use V2 of the Sampler and Estimator classes. Note that, the sampler class does not support quasi-distributions. If you need quasi-distributions, you can code that easily. See Asian Barrier Spreads Tutorial in the Finance toolkit examples for a reference code.

Using the Statevector Sampler

QrStatevectorSampler class is derived from Qiskit BaseSamplerV2 class. The following code illustrates its usage.

# Import from Quantum Rings Toolkit
from quantumrings.toolkit.qiskit import QrRuntimeService

# Acquire Quantum Rings backend
qr_services = QrRuntimeService(token =<YOUR_TOKEN_HERE>, name=<YOUR_ACCOUNT_NAME_HERE>)
qr_backend = qr_services.backend(name = "scarlet_quantum_rings", precision = "single")

from qiskit.circuit import (
    Parameter, QuantumCircuit, ClassicalRegister, QuantumRegister
)

#from qiskit.primitives import StatevectorSampler
from quantumrings.toolkit.qiskit import QrStatevectorSampler

import matplotlib.pyplot as plt
import numpy as np

# Define our circuit registers, including classical registers
# called 'alpha' and 'beta'.
qreg = QuantumRegister(3)
alpha = ClassicalRegister(2, "alpha")
beta = ClassicalRegister(1, "beta")

# Define a quantum circuit with two parameters.
circuit = QuantumCircuit(qreg, alpha, beta)
circuit.h(0)
circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.ry(Parameter("a"), 0)
circuit.rz(Parameter("b"), 0)
circuit.cx(1, 2)
circuit.cx(0, 1)
circuit.h(0)
circuit.measure([0, 1], alpha)
circuit.measure([2], beta)

number_of_params = 100

# Define a sweep over parameter values, where the second axis is over.
# the two parameters in the circuit.
params = np.vstack([
    np.linspace(-np.pi, np.pi, number_of_params),
    np.linspace(-4 * np.pi, 4 * np.pi, number_of_params)
]).T

# Instantiate a new statevector simulation based sampler object.
sampler = QrStatevectorSampler(backend = qr_backend)

# Start a job that will return shots for all 100 parameter value sets.
pub = (circuit, params)
job = sampler.run([pub], shots=256)

# Extract the result for the 0th pub (this example only has one pub).
result = job.result()[0]

# There is one BitArray object for each ClassicalRegister in the
# circuit. Here, we can see that the BitArray for alpha contains data
# for all 100 sweep points, and that it is indeed storing data for 2
# bits over 256 shots.
assert result.data.alpha.shape == (number_of_params,)
assert result.data.alpha.num_bits == 2
assert result.data.alpha.num_shots == 256

# We can work directly with a binary array in performant applications.
raw = result.data.alpha.array

# For small registers where it is anticipated to have many counts
# associated with the same bitstrings, we can turn the data from,
# for example, the 22nd sweep index into a dictionary of counts.
counts = result.data.alpha.get_counts(22)

# Or, convert into a list of bitstrings that preserve shot order.
bitstrings = result.data.alpha.get_bitstrings(22)
print(bitstrings)

You can download this example from the link: Statevector Sampler Example

Using the Statevector Estimator

QrStatevectorEstimator class is derived from Qiskit BaseEstimatorV2 class. The following code illustrates its usage.

# Import from Quantum Rings Toolkit
from quantumrings.toolkit.qiskit import QrRuntimeService

# Acquire Quantum Rings backend
qr_services = QrRuntimeService(token =<YOUR_TOKEN_HERE>, name=<YOUR_ACCOUNT_NAME_HERE>)
qr_backend = qr_services.backend(name = "scarlet_quantum_rings", precision = "single")

from qiskit.circuit import Parameter, QuantumCircuit
from qiskit.quantum_info import Pauli, SparsePauliOp

from quantumrings.toolkit.qiskit import QrStatevectorEstimator

import matplotlib.pyplot as plt
import numpy as np

# Define a circuit with two parameters.
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.ry(Parameter("a"), 0)
circuit.rz(Parameter("b"), 0)
circuit.cx(0, 1)
circuit.h(0)

# Define a sweep over parameter values, where the second axis is over
# the two parameters in the circuit.
params = np.vstack([
    np.linspace(-np.pi, np.pi, 100),
    np.linspace(-4 * np.pi, 4 * np.pi, 100)
]).T

# Define three observables. Many formats are supported here including
# classes such as qiskit.quantum_info.SparsePauliOp. The inner length-1
# lists cause this array of observables to have shape (3, 1), rather
# than shape (3,) if they were omitted.
observables = [
    [SparsePauliOp(["XX", "IY"], [0.5, 0.5])],
    [Pauli("XX")],
    [Pauli("IY")]
]

# Instantiate a new statevector simulation based estimator object.
estimator = QrStatevectorEstimator(backend = qr_backend)

# Estimate the expectation value for all 300 combinations of
# observables and parameter values, where the pub result will have
# shape (3, 100). This shape is due to our array of parameter
# bindings having shape (100,), combined with our array of observables
# having shape (3, 1)
pub = (circuit, observables, params)
job = estimator.run([pub])

# Extract the result for the 0th pub (this example only has one pub).
result = job.result()[0]

# Error-bar information is also available, but the error is 0
# for this StatevectorEstimator.
result.data.stds

# Pull out the array-based expectation value estimate data from the
# result and plot a trace for each observable.
for idx, pauli in enumerate(observables):
    plt.plot(result.data.evs[idx], label=pauli)
plt.legend()

You can download this example from the link: Statevector Estimator Example