Labelling workflow data in Nexus using Properties¶
In this example, we’ll submit a few different circuits on a few different simulators and use Nexus Properties to label our data.
These properties can then be used to filter our results when we make queries to Nexus.
from datetime import datetime
from pytket import Circuit
from pytket.pauli import Pauli
from pytket.circuit import PauliExpBox, QControlBox
import qnexus as qnx
from qiskit_aer import noise
my_project = qnx.projects.get_or_create(name="My Properties Example Project")
qnx.context.set_active_project(my_project)
# Add some properties to the project
qnx.projects.add_property(
name="name_qpu",
description="The name of the device used for a job",
property_type="string",
)
qnx.projects.add_property(
name="circuit_type",
description="The type of circuit ran, either 'pauli_gadget' or 'bell_state'",
property_type="string",
)
qnx.projects.add_property(
name="noisy",
description="Was the circuit run with noise?",
property_type="bool",
)
qnx.projects.add_property(
name="num_shots",
description="Shots run when executing the circuit",
property_type="int",
)
Create our circuits¶
Here we’ll create two simple ‘types’ of circuit.
bell_circuit = Circuit(2, 2).H(0).CX(0, 1).measure_all()
bell_circuit_ref = qnx.circuits.upload(
circuit=bell_circuit,
name="Bell State Circuit",
properties={"circuit_type": "bell_state"},
)
pauli_gadget_circuit = Circuit(5,5)
zzyx_box = PauliExpBox([Pauli.Z, Pauli.Z, Pauli.Y, Pauli.X], 0.7)
# Controlled Pauli gadget with a single control.
controlled_pauli = QControlBox(zzyx_box, 1)
pauli_gadget_circuit.H(0).add_gate(controlled_pauli, [0,1,2,3,4]).measure_all()
pauli_circuit_ref =qnx.circuits.upload(
circuit=pauli_gadget_circuit,
name="Pauli Gadget Circuit",
properties={"circuit_type": "pauli_gadget"},
)
Define a simple workflow to run on Nexus-hosted simulators¶
# Double check the properties defined on the project (currently set in the context)
qnx.projects.get_properties().df()
def run_workflow(
config: qnx.BackendConfig,
num_shots: int,
):
device_name = qnx.context.get_active_properties().get("name_qpu")
compiled_pauli_refs = qnx.compile(
circuits=[pauli_circuit_ref],
name=f"Pauli Gadget Circuit Compilation for {device_name} on {datetime.now()}",
properties={"circuit_type": "pauli_gadget"},
backend_config=config
)
compiled_reg_refs = qnx.compile(
circuits=[bell_circuit_ref],
name=f"Bell Circuit Compilation for {device_name} on {datetime.now()}",
properties={"circuit_type": "bell_state"},
backend_config=config
)
with qnx.context.using_properties(
num_shots=num_shots,
):
qnx.start_execute_job(
circuits=compiled_pauli_refs,
n_shots=[num_shots],
name=f"Pauli Gadget Circuit Execution for {device_name} on {datetime.now()}",
properties={"circuit_type": "pauli_gadget"},
backend_config=config,
)
qnx.start_execute_job(
circuits=compiled_reg_refs,
n_shots=[num_shots],
name=f"Bell State Circuit Execution for {device_name} on {datetime.now()}",
properties={"circuit_type": "bell_state"},
backend_config=config,
)
# Run on H1-Emulator
with qnx.context.using_properties(name_qpu="H1-Emulator"):
with qnx.context.using_properties(noisy=True):
run_workflow(
config = qnx.QuantinuumConfig(device_name="H1-Emulator"),
num_shots=100,
)
with qnx.context.using_properties(noisy=False):
run_workflow(
config = qnx.QuantinuumConfig(device_name="H1-Emulator", noisy_simulation=False),
num_shots=100,
)
qnx.projects.summarize(my_project)
# Simple qiskit aer noise model
prob_ro = 1e-3
n_qubits = 5
noise_model = noise.NoiseModel()
probabilities = [[1 - prob_ro, prob_ro], [prob_ro, 1 - prob_ro]]
error_ro = noise.ReadoutError(probabilities)
for i in range(n_qubits):
noise_model.add_readout_error(error_ro, [i])
# Run on the Aer simulator
with qnx.context.using_properties(name_qpu="Aer"):
with qnx.context.using_properties(noisy=True):
run_workflow(
config = qnx.AerConfig(noise_model=noise_model),
num_shots=100,
)
with qnx.context.using_properties(noisy=False):
run_workflow(
config = qnx.AerConfig(),
num_shots=100,
)
qnx.projects.summarize(my_project)
Use properties to filter and compare our results¶
Using the ‘properties’ filter when retrieving jobs, we can obtain the results we want to compare.
jobs_query = qnx.jobs.get_all(
job_type=["execute"],
properties={
"circuit_type": "bell_state",
# Uncomment the below line to filter by the name of the QPU
#"name_qpu": "H1-Emulator",
"noisy": False,
}
)
jobs_query.df()
# Download the results and print the counts side by side for comparison
results = []
labels = []
for job in jobs_query.list():
results.append(qnx.jobs.results(job)[0].download_result())
qpu_name = job.annotations.properties.get("name_qpu")
noisy = job.annotations.properties.get("noisy")
labels.append(f"{qpu_name} {'noisy' if noisy else 'noiseless'}")
for result, label in zip(results, labels):
print(f"{label}")
print(result.get_counts())