2. Heterogeneous Quantum Program Design¶
Heterogeneous quantum program design isn't mysterious. It simply involves taking classical program parameters and inputting them into the quantum program (such as single-qubit rotation angles), and then providing the quantum program's results to the classical program as needed.
2.1 Example of Heterogeneous QCIS Program¶
2.1.1 Example 1: Passing Classical Parameters into a Quantum Circuit¶
The demonstration program may not have practical physical significance and is provided for reference in programming style.
from pyezQ import * #Import the pyezQ package
import time
account = Account(login_key='554393d4e2425130b0bcf7579163ffe2', machine_name='Transponder')
#For instance, set the user SDK key, and select a quantum computer.
create_res = account.create_experiment('expe21000010')
if create_res == 0:
print('Failed to create a new experiment collection.')Import the pyezQ package
else :
print('Successfully created a new experiment collection,ID=', create_res)
lab_id = create_res
#In the circuit, pre-set {x} represents the parameters to be passed.
qcis_circuit = '''
RX Q0 {n1}
RX Q6 {n2}
H Q0
X Q6
H Q6
CZ Q0 Q6
H Q6
M Q0
M Q6
'''
#The code embeds variables ['n1', 'n2'], and by using the 'submit_job' function, parameters are passed before submission to achieve dynamic data input.
value=0
while value < 0.5 : #Classical computation conditionals
query_id = account.submit_job(circuit=qcis_circuit, lab_id=lab_id, version=str(int(time.time())),
parameters=[['n1','n2']], values=[[(0.2*value)%3.14, (0.2*value)%3.14]], num_shots=100)
#Passing real-time classical data into the quantum program and executing it.
#Realized the interaction between classical program data and quantum program data.
#Additionally, it's possible to call different quantum programs with varying parameters based on classical data conditions. Refer to Example 2.
if query_id:
result=account.query_experiment(query_id, max_wait_time=360000)
#The maximum waiting time is in seconds, and it defaults to 30 seconds when not specified. Quantum program execution may involve queuing, and the quantum computer itself has its automatic calibration time. If you intend to run a fully automated program, it's advisable to set the waiting time greater than both of these.
print(result)
value = float(result[0]['probability']['00']) #Process the quantum program's execution results and assign them back to the classical program.
print(value)
else:
value=0.5
print(f'Iteration failed, some experiments did not run successfully.')
#Below is the use and storage of the computation results.
f = open("./results.txt",'w')
f.write('value={},next n1={},n2={}'.format(value,(0.2*value)%3.14, (0.2*value)%3.14))
f.close()
2.1.2 Example 2: Using classical parameters as conditional statements to execute different quantum circuits.¶
The demonstration program may not necessarily have practical physical significance; it is provided for reference in programming style.
from pyezQ import * #Import the pyezQ package
import time
account = Account(login_key='554393d4e2425130b0bcf7579163ffe2', machine_name='Transponder')
#Create an instance, set the user SDK key, and select a quantum computer.
create_res = account.create_experiment('expe21000009')
if create_res == 0:
print('Failed to create a new experiment collection')
else :
print('Successfully created a new experiment collection,ID=', create_res)
lab_id = create_res
qcis_circuit_1 = '''
RX Q0 {n1}
RX Q6 {n2}
H Q0
X Q6
H Q6
CZ Q0 Q6
H Q6
M Q0
M Q6
'''
#The code embeds variables ['n1', 'n2'], and by using the 'submit_job' function, parameters are passed before submission to achieve dynamic data input.
qcis_circuit_2 = '''
H Q0
X Q6
H Q6
CZ Q0 Q6
H Q6
RX Q0 {n3}
RX Q6 {n4}
M Q0
M Q6
'''
#The code embeds variables ['n3', 'n4'], and by using the 'submit_job' function, parameters are passed before submission to achieve dynamic data input.
#A series of classical computations to obtain a conditional variable.
value=0
if value < 0.5 : #Classical computation conditionals
query_id = account.submit_job(circuit=qcis_circuit_1, lab_id=lab_id, version="GPT4"+str(int(time.time())),
parameters=[['n1','n2']], values=[[(0.2*value)%3.14, (0.2*value)%3.14]])
#Passing real-time classical data into the quantum program and executing it.
#Realized the interaction between classical program data and quantum program data.
if query_id:
result=account.query_experiment(query_id, max_wait_time=360000)
#The maximum waiting time is in seconds, and it defaults to 30 seconds when not specified. Quantum program execution may involve queuing, and the quantum computer itself has its automatic calibration time. If you intend to run a fully automated program, it's advisable to set the waiting time greater than both of these.
print(result)
value = float(result[0]['probability']['00']) #Process the quantum program's execution results and assign them back to the classical program.
print(value)
else:
print(f'Iteration failed, some experiments did not run successfully.')
else:
query_id = account.submit_job(circuit=qcis_circuit_2, lab_id=lab_id, version="GPT4"+str(int(time.time())),
parameters=[['n1','n2']], values=[[(0.2*value)%3.14, (0.2*value)%3.14]])
#Passing real-time classical data into the quantum program and executing it.
#Realized the interaction between classical program data and quantum program data.
if query_id:
result=account.query_experiment(query_id, max_wait_time=360000)
#The maximum waiting time is in seconds, and it defaults to 30 seconds when not specified. Quantum program execution may involve queuing, and the quantum computer itself has its automatic calibration time. If you intend to run a fully automated program, it's advisable to set the waiting time greater than both of these.
print(result)
value = float(result[0]['probability']['00']) #Process the quantum program's execution results and assign them back to the classical program.
print(value)
else:
print(f'Iteration failed, some experiments did not run successfully')
#Furthermore, it's possible to perform additional iterations based on the experiment results, among other actions. Refer to Example 1 for more details.
#Below is the utilization and preservation of computational results.
f = open("./results.txt",'w')
f.write('value={},next n1={},n2={}'.format(value,(0.2*value)%3.14, (0.2*value)%3.14))
f.close()
2.1.3 Example 3: Reconstructing (assembling) quantum circuits based on classical parameters¶
The demonstration program may not necessarily have practical physical significance, it is provided for reference in programming style.
from pyezQ import * #Import the pyezQ package
import time
account = Account(login_key='554393d4e2425130b0bcf7579163ffe2', machine_name='Transponder')
#Create an instance, set the user SDK key, and select a quantum computer
create_res = account.create_experiment('expe21000009')
if create_res == 0:
print('Failed to create a new experiment collection')
else :
print('Successfully created a new experiment collection,ID=', create_res)
lab_id = create_res
qcis_circuit = '''
'''
#Blank quantum circuit, awaiting generation
#A series of classical computations
i=15
if i >10:
qcis_circuit=qcis_circuit+'\nX Q0'
else:
qcis_circuit=qcis_circuit+'\Y Q0'
#Another series of classical computations
j=5
if j >10:
qcis_circuit=qcis_circuit+'\nRX Q0 {n1} \nRY Q0 {n2} \nM Q0'
else:
qcis_circuit=qcis_circuit+'\nRY Q0 {n1} \nRX Q0 {n2} \nM Q0'
#Let's see what the circuit looks like now
print(qcis_circuit)
#Yet another series of classical computations
value=0
#Using quantum experiment results recursively and classical parameter passing as the basis for the following examples.
while value < 0.5 : #Classical computation conditionals
query_id = account.submit_job(circuit=qcis_circuit, lab_id=lab_id, version="GPT4"+str(int(time.time())),parameters=[['n1','n2']], values=[[(0.2*value)%3.14, (0.2*value)%3.14]], num_shots=100)
#Passing real-time classical data into the quantum program and executing it.
#Realized the interaction between classical program data and quantum program data.
#Additionally, it's possible to call different quantum programs with varying parameters based on classical data conditions. Refer to Example 2
if query_id:
result=account.query_experiment(query_id, max_wait_time=360000)
#The maximum waiting time is in seconds, and it defaults to 30 seconds when not specified. Quantum program execution may involve queuing, and the quantum computer itself has its automatic calibration time. If you intend to run a fully automated program, it's advisable to set the waiting time greater than both of these.
print(result)
value = float(result[0]['probability']['0']) #Process the quantum program's execution results and assign them back to the classical program.
print(value)
else:
value=0.5
print(f'Iteration failed, some experiments did not run successfully')
#Below is the use and storage of the computation results.
f = open("./results.txt",'w')
f.write('value={},next n1={},n2={}'.format(value,(0.2*value)%3.14, (0.2*value)%3.14))
f.close()
2.2 Example of Heterogeneous Quingo Program¶
The following example code is excerpted from the Quingo open-source library's samples, https://gitee.com/quingo/quingo-runtime/tree/master/src/examples/H2_VQE
#The core parameterized quantum algorithm is in the 'kernel.qu' file
#Below, we will display it to explain to everyone. The comments have been added for the purpose of this tutorial and are for reference only.
quingo_circuit='''
opaque X(q: qubit) : unit;
opaque X2P(q: qubit) : unit;
opaque X2M(q: qubit) : unit;
opaque Y2P(q: qubit) : unit;
opaque Y2M(q: qubit) : unit;
opaque RZ(q: qubit, angle: double) : unit;
opaque CZ(q1: qubit, q2: qubit) : unit;
opaque measure(q: qubit): bool;
#Custom CNOT gate
operation CNOT(a: qubit, b: qubit) : unit {
Y2M(b);
CZ(a, b);
Y2P(b);
}
#Custom bit initialization operation
operation init(q: qubit): unit{
X(q);
}
#Custom parameterized Ansatz function, with 'angle' introduced as a classical variable into the quantum program.
operation ansatz(angle: double): unit {
using(q0: qubit, q1: qubit) {
init(q0);
X2M(q0);
Y2P(q1);
CNOT(q1, q0);
RZ(q0, angle);
CNOT(q1, q0);
X2P(q0);
Y2M(q1);
}
}
'''
The classical program is located in the 'host.py' file. The following provides an explanation of certain code for heterogeneous programming.
#The local invocation relationship related to quantum is eval_all() --> energy_theta(theta, g) --> get_ansatz("ansatz", theta) --> qi.call_quingo(qu_file, circ_name, theta)
#The original example is ultimately returned by the 'get_ansatz' function, providing the simulation results of the quantum circuit with angle parameters.
#You just need to replace the results obtained by the simulator with the results obtained by the physical machine in the 'get_ansatz' function.
#This module should not be executed
def get_ansatz(circ_name, theta):
if not qi.call_quingo(qu_file, circ_name, theta):
print("Failed to call {}".format(circ_name))
res = qi.read_result()
return res
def energy_theta(theta: np.double, g):
'''Return the calculated energy for the given parameter theta.
'''
ansatz_state = get_ansatz("ansatz", theta)
h = hamiltonian(g)
energy = expectation(h, ansatz_state)
return energy
def eval_all():
bond_length = []
lowest_energies = []
# theta = -np.pi/2
for b in bond_h_decompose:
bond_length.append(b[0])
g = b[1:]
# --------------- brute-force scanning - --------------
# angles = np.linspace(-np.pi/2, np.pi/2, 50)
# tmp_lowest_energies = [energy_theta(theta, g) for theta in angles]
# ele_tmp_lowest_energies = (min(tmp_lowest_energies)).A[0][0]
# lowest_energies.append(ele_tmp_lowest_energies)
# # --------------- optimization based on searching ---------------
minimum = minimize_scalar(
lambda theta: (energy_theta(theta, g)).A[0][0])
lowest_energies.append(minimum.fun)
plt.plot(bond_length, lowest_energies, "b.-")
plt.xlabel("Bond Length")
plt.title("Variational Quantum Eigensolver")
plt.ylabel("Energy")
plt.show()
The local invocation relationship related to quantum is eval_all() --> energy_theta(theta, g) --> get_ansatz("ansatz", theta) --> qi.call_quingo(qu_file, circ_name, theta)
The original example is ultimately returned by the 'get_ansatz' function, providing the simulation results of the quantum circuit with angle parameters.
You just need to replace the results obtained by the simulator with the results obtained by the physical machine in the 'get_ansatz' function.
#This module should not be executed
from pyezQ import * #Import the pyezQ package
account = Account(login_key='4f90473cc1cf4fa3d9a7b146c1524e7f', machine_name='Transponder')
#Create an instance, set the user SDK key, and select a quantum computer
create_res = account.create_experiment('expe21000009')
if create_res == 0:
print('Failed to create a new experiment collection')
else :
print('Successfully created a new experiment collection,ID=', create_res)
lab_id = create_res
def get_ansatz(circ_name, theta):
if not qi.call_quingo(qu_file, circ_name, theta):
print("Failed to call {}".format(circ_name))
f = open("./build/{}.qcis".format(circ_name),'r')
quingo_qcis=f.read(100000)#Ensure that the read length exceeds the total file length.
f.close()
print(quingo_qcis)
query_id_quingo = account.submit_job(circuit=quingo_qcis, version=circ_name)
if query_id_quingo:
result=account.query_experiment(query_id_quingo, max_wait_time=360000)
#The maximum waiting time is in seconds, and it defaults to 30 seconds when not specified. Quantum program execution may involve queuing, and the quantum computer itself has its automatic calibration time. If you intend to run a fully automated program, it's advisable to set the waiting time greater than both of these.
#res = qi.read_result()
res = result #Note that the required form of 'res' here may not be correct in this processing
return res
Because VQE experiments involve multiple calls to the quantum device and take a significant amount of time, we won't demonstrate the entire process here. Interested users can modify the code and conduct comparative experiments.
Thus, the heterogeneous programming application of Quingo, specifically the hydrogen molecule VQE experiment, has been successfully executed on a quantum computing physical machine.
Other program requirements can also incorporate a similar parameter introduction process for design.
2.3 Example of Heterogeneous Programming with isQ¶
Both programming methods in isQ can achieve parameterized programming.
First, directly using parameters in the code string and passing specific values at compile time.
from isq import LocalDevice
isq_code = '''
qbit q[2];
RX(theta, q[0]);
CNOT(q[0],q[1]);
M(q[0,1]);
'''
#Obtaining QCIS code
ld = LocalDevice()
ir = ld.compile_to_ir(isq_code, target = 'qcis', theta = 1.234)
print(ir)
The example code below demonstrates using isQ to find the lowest energy of a hydrogen molecule based on the Variational Quantum Eigensolver (VQE) algorithm. (Please note that executing the code below may take several seconds, depending on your classical computer's performance. It's not a program error; please wait for the results.)
from isq import LocalDevice
isq_code = '''
qbit q[2];
X(q[1]);
RY(1.57,q[0]);
RX(4.71, q[1]);
CNOT(q[0],q[1]);
RZ(theta,q[1]);
CNOT(q[0],q[1]);
RY(4.71, q[0]);
RX(1.57, q[1]);
if(e_n == 0){
M(q[0]);
}
if(e_n==1){
M(q[1]);
}
if(e_n==2){
M(q[0,1]);
}
if(e_n==3){
RX(1.57, q[0]);
RX(1.57, q[1]);
M(q[0,1]);
}
if(e_n==4){
H(q[0,1]);
M(q[0,1]);
}
'''
ld = LocalDevice()
def get_exception(theta)->float :
'''
"thetas" in the context of angle preparation
e_n<= E_N,Measure the nth energy, e
'''
theta = float(theta)
E_N=5
exceptions = list()
hs=[-0.4804,+0.3435,-0.4347,+0.5716,+0.0910,+0.0910]
exceptions.append(hs[0])
for e_n in range(E_N) :
test_res=ld.run(isq_code,theta=theta, e_n=e_n)
exception = 0
for measure_res in test_res :
frequency = test_res[measure_res]/100
#Replace probability with frequency
parity = (-1)**(measure_res.count('1')%2)
#Parity check
exception += parity*frequency
exceptions.append(hs[e_n+1]*exception)
return sum(exceptions)
# nelder-mead optimization of a convex function
from scipy.optimize import minimize
from numpy.random import rand
# define range for theta
theta_min, theta_max = -3.14, 3.14
# define the starting point as a random sample from the domain
pt = theta_min + rand(1) * (theta_max - theta_min)
# perform the search
result = minimize(get_exception, pt, method='nelder-mead')
# summarize the result
print(f"Status : {result['message']}")
print(f"Total Evaluations: {result['nfev']}")
# evaluate solution
solution = result['x']
evaluation = get_exception(solution)
print(f"Solution: H_2({solution}) = {evaluation} Eh")
This core function is run on a simulator. If you want to use quantum hardware, you can obtain the compiled IR as described in section 2.3 and then use the platform API to run it.