Parameters and upgrade_parameters
Parameters are placeholders for the values of parametric gates like rx, rzz or u3. They are designed to facilitate the execution of the same circuit multiple times with different values on its parametric gates.
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