Parameters and upgrade_parameters
CUNQA provides convenient ways of improving the implementation of Variational Quantum Algorithms (VQAs). VQAs require vast amounts of circuit evaluations for their optimization processes, where the circuit structure remains constant and solely the parameters of certain gates change between evaluations. CUNQA facilitates working with these type of template circuits by its support for Parameters.
Parameters are placeholders for the values that will be inserted on parametric gates at each evaluation. They can be single variables, say x or expressions with multpile variables like cos(2*x) + exp(z/2).
Parameters are inserted as a string in parametric gates to mark that its value will vary, and their value
is given when executing. After running a parametric circuit, new parameters can be given for another
evaluation using upgrade_parameters. If a value is not given to a
certain variable, it will retain the value from last evaluation.
circuit.rx(param="x", qubit=0)
# Parameters are given values when running
qjob = run(circuit, qpu, param_values={"x": np.pi}, shots= 1024)
result = qjob.result
# For another execution with new parameters, use QJob.upgrade_parameters()
new_result = qjob.upgrade_parameters({"x": 0}).result
Parameters are given as a string when adding a gate. The strings determine the label for each parameter or a expression built out of parameters:
circuit.rx(param="x", qubit=0)
circuit.rz(param="x + y + z", qubit=0) # Parameter expression with 3 variables. the "x" variable has the same value as in the previous gate
circuit.ry(param="cos(2*pi*z))", qubit=0) # Common functions can be used, and pi is interpreted as 3.1415..., the ratio of a circle's circumference to its diameter.
Internally, parameters and parameter expressions are handled with the symbolic calculus library sympy so valid strings are restricted by the sympify function that we use to convert to sympy objects. The functions recognized include trigonometric, hyperbolic, exponetial and logarithmic ones. Check a more complete list at the sympy documentation .
Values for parameters are giving as an argument of the run function, which can be a dict of a list. The dict would contain as keys strings with the labels of the variables present across all parameters with their corresponding int or float value associated, whereas the list would contain the values of the parameters in order. Dict format is preferrable when complex expressions and repeated parameters appear, while the list is fast in cases where there are no repeated parameters and each parameter contains a single variable.
# Parameters are given values when running
run(circuit, qpu, param_values={"x": np.pi, "y": 0, "z": 4.5}, shots= 1024)
run(circuit, qpu, param_values=[np.pi, 0, 4.5], shots= 1024)
For evaluating the same circuit that has been run but with new parameters, use upgrade_parameters
on its associated QJob object, where the parameters can be given again as a
list or a dict. Note that variables that are not given a new value keep the previous one.
# For another execution with new parameters, use QJob.upgrade_parameters()
qjob.upgrade_parameters({"x": 1004, "y": np.pi/4, "z": 4})
qjob.upgrade_parameters([ 1004, np.pi/4, 4]) # Same with list
qjob.upgrade_parameters({"x": 1004}) # Upgrade just the value for "x"
Check the following complete example:
import os, sys
# In order to import cunqa, we append to the search path the cunqa installation path
sys.path.append(os.getenv("HOME")) # HOME as install path is specific to CESGA
from cunqa.qpu import get_QPUs, qraise, qdrop, run
from cunqa.circuit import CunqaCircuit
import numpy as np
green_txt = '\033[92m'; reset = '\033[0m'
try:
# 1. Deploy QPU
# If GPU execution is desired, just add "gpu = True" as another qraise argument
family = qraise(1, "00:10:00", co_located = True)
except Exception as error:
raise error
try:
qpu = get_QPUs(co_located = True, family = family)
# ---------------------------
# 2. Design circuit:
# circ_upgrade.q0 ─[RX(cos(x))]─────[M]─
#
# circ_upgrade.q1 ─[RX(y)]──────────[M]─
#
# circ_upgrade.q2 ─[RX(z)]──────────[M]─
# ---------------------------
circ_upgrade = CunqaCircuit(3)
circ_upgrade.rx("pi*cos(x)", 0)
circ_upgrade.rx("y", 1)
circ_upgrade.rx("z", 2)
circ_upgrade.measure_all()
# 3. Execute circuit
qjob = run(circ_upgrade, qpu, param_values={"x": np.pi, "y": 0, "z": 0}, shots=1024)
print(f"{green_txt}Result 0: {qjob.result.counts}{reset}")
# Upgrade with dicts
qjob.upgrade_parameters({"x": np.pi/2, "y": 0, "z": 0})
print(f"{green_txt}Result 1: {qjob.result.counts}{reset}")
# Upgrade with a dict with only some of the Variables (previous values are preserved)
qjob.upgrade_parameters({"y": np.pi})
print(f"{green_txt}Result 2: {qjob.result.counts}{reset}")
# Now with a list (in the order in which the parameters where added to the circuit)
qjob.upgrade_parameters([0, 0, np.pi])
print(f"{green_txt}Result 3: {qjob.result.counts}{reset}")
# 4. Relinquish resources
qdrop(family)
except Exception as error:
qdrop(family)
raise error