from genQC.imports import *
import genQC.utils.misc_utils as util
from genQC.dataset.config_dataset import ConfigDataset
from genQC.pipeline.multimodal_diffusion_pipeline import MultimodalDiffusionPipeline_ParametrizedCompilation
from genQC.scheduler.scheduler_dpm import DPMScheduler
from genQC.platform.tokenizer.circuits_tokenizer import CircuitTokenizer
from genQC.platform.simulation import Simulator, CircuitBackendType
from genQC.inference.sampling import decode_tensors_to_backend, generate_compilation_tensors
from genQC.inference.evaluation_helper import get_unitaries
from genQC.inference.eval_metrics import UnitaryInfidelityNorm
from genQC.dataset.balancing import get_tensor_gate_length
Compile unitaries with parametrized circuits
# clean existing memory alloc
util.MemoryCleaner.purge_mem() = util.infer_torch_device() # use cuda if we can
device device
[INFO]: Cuda device has a capability of 8.6 (>= 8), allowing tf32 matmul.
device(type='cuda')
# We set a seed to pytorch, numpy and python.
# Note: This will also set deterministic algorithms, possibly at the cost of reduced performance!
0) util.set_seed(
Load model
Load the pre-trained model directly from Hugging Face: Floki00/cirdit_multimodal_compile_3to5qubit.
= MultimodalDiffusionPipeline_ParametrizedCompilation.from_pretrained("Floki00/cirdit_multimodal_compile_3to5qubit", device) pipeline
The model is trained with the gate set:
pipeline.gate_pool
['h', 'cx', 'ccx', 'swap', 'rx', 'ry', 'rz', 'cp']
which we need in order to define the vocabulary
, allowing us to decode tokenized circuits.
= {g:i+1 for i, g in enumerate(pipeline.gate_pool)}
vocabulary = CircuitTokenizer(vocabulary)
tokenizer tokenizer.vocabulary
{'h': 1, 'cx': 2, 'ccx': 3, 'swap': 4, 'rx': 5, 'ry': 6, 'rz': 7, 'cp': 8}
Set inference parameters
Set diffusion model inference parameters.
= DPMScheduler.from_scheduler(pipeline.scheduler)
pipeline.scheduler = DPMScheduler.from_scheduler(pipeline.scheduler_w)
pipeline.scheduler_w
= 40
timesteps
pipeline.scheduler.set_timesteps(timesteps)
pipeline.scheduler_w.set_timesteps(timesteps)
= 1.0
pipeline.lambda_h = 0.35
pipeline.lambda_w = 0.3
pipeline.g_h = 0.1 pipeline.g_w
We assume in this tutorial circuits of 4 qubits.
= 32 # How many circuits we sample per unitary
num_of_samples_per_U = 4
num_of_qubits
= "Compile 4 qubits using: ['h', 'cx', 'ccx', 'swap', 'rx', 'ry', 'rz', 'cp']"
prompt
# These parameters are specific to our pre-trained model.
= 5
system_size = 32 max_gates
For evaluation, we also need a circuit simulator backend.
= Simulator(CircuitBackendType.QISKIT) simulator
Load test unitaries
We load a balanced testset directly from Hugging Face: Floki00/unitary_compilation_testset_3to5qubit.
= ConfigDataset.from_huggingface("Floki00/unitary_compilation_testset_3to5qubit", device="cpu") testset
We pick the 4 qubit circuits as test cases for this tutorial.
= testset.xs_4qubits # tokenized circuit
target_xs = testset.ps_4qubits # circuit angle paramters
target_ps = testset.us_4qubits.float() # corresponding unitaries, target_us
For 4 qubits the unitary is a 16x16 matrix. Complex numbers are split into 2 channels (real, imag).
# [batch, 2, 2^n, 2^n] target_us.shape
torch.Size([3947, 2, 16, 16])
A random circuit may look like this:
= torch.randint(target_us.shape[0], (1, ))
rnd_index
= decode_tensors_to_backend(simulator, tokenizer, target_xs[rnd_index], target_ps[rnd_index])
qc_list, _ 0].draw("mpl") qc_list[
Next, we further restrict to circuits with a maximum of 16 gates.
= get_tensor_gate_length(target_xs)
gate_cnts
= (gate_cnts <= 16).nonzero().squeeze()
ind = target_xs[ind]
target_xs = target_ps[ind]
target_ps = target_us[ind] target_us
We plot the distribution of the gate counts for this testset, seeing it is uniformly balanced.
= get_tensor_gate_length(target_xs)
gate_cnts
= np.bincount(gate_cnts)
d range(d.size), d)
plt.bar("Number of gates", fontsize=13)
plt.xlabel("Frequency", fontsize=13)
plt.ylabel( plt.show()
Compile a single unitary
First, we want to compile a single unitary for 4 qubits from the testset. We pick one with 8 gates.
= (gate_cnts == 8).nonzero().squeeze()[:1]
ind
= decode_tensors_to_backend(simulator, tokenizer, target_xs[ind], target_ps[ind])
qc_list, _ 0].draw("mpl") qc_list[
= target_us[ind].squeeze()
U
= generate_compilation_tensors(pipeline,
out_tensor, params =prompt,
prompt=U,
U=num_of_samples_per_U,
samples=system_size,
system_size=num_of_qubits,
num_of_qubits=max_gates,
max_gates=False, # show progress bar
no_bar )
[INFO]: (generate_comp_tensors) Generated 32 tensors
For instance, a circuit tensor alongside parameters the model generated looks like this
print(out_tensor[0])
print(params[0])
tensor([[ 7, 8, 0, -3, 1, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9],
[ 0, 8, 0, -3, 0, 7, 4, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9],
[ 0, 0, 4, 3, 0, 0, 4, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9],
[ 0, 0, 4, 0, 0, 0, 0, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9]], device='cuda:0')
tensor([[ 0.2794, 0.1956, 0.0000, 0.0000, 0.0000, -0.3857, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000]], device='cuda:0')
Evaluate and plot circuits
We decode these now to circuits and calculate their unitaries
= decode_tensors_to_backend(simulator, tokenizer, out_tensor, params)
generated_qc_list, _ = get_unitaries(simulator, generated_qc_list) generated_us
We then evaluate the unitary infidelity to our target U
.
= UnitaryInfidelityNorm.distance(
U_norms =torch.from_numpy(np.stack(generated_us)).to(torch.complex128),
approx_U=torch.complex(U[0], U[1]).unsqueeze(0).to(torch.complex128),
target_U )
We plot the four best ciruits, w.r.t. the infidelity:
= 4
plot_k_best
= np.argsort(U_norms)
idx = plt.subplots(1, plot_k_best, figsize=(10, 2), constrained_layout=True, dpi=150)
fig, axs
for i, (idx_i, ax) in enumerate(zip(idx[:plot_k_best], axs.flatten())):
ax.clear()"mpl", plot_barriers=False, ax=ax)
generated_qc_list[idx_i].draw(f"The {i+1}. best circuit: \n infidelity {U_norms[idx_i]:0.1e}.", fontsize=10) ax.set_title(
Compile testset unitaries
To get an overall performance estimation, we compile multiple unitaries, record the best infidelities and plot the distribution.
Generate tensors
To keep the tutorial short in computation time, we only take a few unitaries here, but this can be adjusted by the user to use the full testset.
= target_us[:16] Us
= []
best_infidelities
for U in tqdm(Us):
= generate_compilation_tensors(pipeline,
out_tensor, params =prompt,
prompt=U,
U=num_of_samples_per_U,
samples=system_size,
system_size=num_of_qubits,
num_of_qubits=max_gates
max_gates
)
= decode_tensors_to_backend(simulator, tokenizer, out_tensor, params)
generated_qc_list, _ = get_unitaries(simulator, generated_qc_list)
generated_us
= UnitaryInfidelityNorm.distance(
U_norms =torch.from_numpy(np.stack(generated_us)).to(torch.complex128),
approx_U=torch.complex(U[0], U[1]).unsqueeze(0).to(torch.complex128),
target_U
)
min()) best_infidelities.append(U_norms.
Plot infidelities
For the compiled unitaries, we get the following distribution of the best infidelities.
=(7, 3), constrained_layout=True, dpi=150)
plt.figure(figsizef"Best infidelities of {len(best_infidelities)} unitaries, with {num_of_samples_per_U} circuits sampled per unitary.")
plt.title(=13)
plt.xlabel(UnitaryInfidelityNorm.name(), fontsize"Frequency", fontsize=13)
plt.ylabel(=60)
plt.hist(best_infidelities, bins-0.05, 1.05])
plt.xlim([ plt.show()
import genQC
print("genQC Version", genQC.__version__)
genQC Version 0.2.0