from genQC.imports import *
import genQC.utils.misc_utils as util
from genQC.pipeline.diffusion_pipeline import DiffusionPipeline
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 qiskit import QuantumCircuit
import qiskit.quantum_info as qi
Compile unitaries
= util.infer_torch_device() # use cuda if we can
device # clean existing memory alloc util.MemoryCleaner.purge_mem()
[INFO]: Cuda device has a capability of 8.6 (>= 8), allowing tf32 matmul.
# 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(
Setup and load
Load the pre-trained model directly from Hugging Face: Floki00/qc_unitary_3qubit.
= DiffusionPipeline.from_pretrained("Floki00/qc_unitary_3qubit", device, ) pipeline
Set 20 sample steps and use rescaled guidance-formula.
= "rescaled"
pipeline.guidance_sample_mode 20)
pipeline.scheduler.set_timesteps(= 10 g
The model was trained with a gate pool of:
pipeline.gate_pool
['h', 'cx', 'z', 'x', 'ccx', 'swap']
= {g:i+1 for i, g in enumerate(pipeline.gate_pool)}
vocabulary = CircuitTokenizer(vocabulary)
tokenizer = Simulator(CircuitBackendType.QISKIT) simulator
Compile a unitary
Compile a given unitary \(U\). Note, there has to be a solution with the pipeline.gate_pool
in order to find the exact solution.
def compile_and_plot(U, prompt):
= torch.Tensor(np.real(U)), torch.Tensor(np.imag(U))
U_r, U_i = torch.stack([U_r, U_i], dim=0)
U_tensor
= generate_compilation_tensors(pipeline,
out_tensor =prompt,
prompt=U_tensor,
U=samples,
samples=num_of_qubits,
system_size=num_of_qubits,
num_of_qubits=max_gates,
max_gates=g,
g=False,
no_bar=False,
tensor_prod_pad=False)
enable_params
= out_tensor.unique(dim=0)
out_tensor
= decode_tensors_to_backend(simulator, tokenizer, out_tensor)
qc_list, error_cnt
= get_unitaries(simulator, qc_list)
approx_Us = torch.from_numpy(np.stack(approx_Us)).to(torch.complex128)
approx_Us = torch.complex(U_r, U_i).unsqueeze(0).to(torch.complex128)
target_Us
= UnitaryInfidelityNorm.distance(target_Us, approx_Us)
U_norms
= ( U_norms.abs() < 1.0e-3 )
corr = [qc for qc, c in zip(qc_list, corr) if c]
corr_qc = sorted(corr_qc, key=lambda x: len(x.data)) # sort to get the shortest solutions
corr_qc
= plt.subplots(1,4, figsize=(12, 4), constrained_layout=True, dpi=150)
fig, axs 0].set_title(f"{prompt}")
axs[for qc,ax in zip(corr_qc, axs.flatten()):
"mpl", plot_barriers=False, ax=ax)
qc.draw( plt.show()
= 512
samples = 3
num_of_qubits = 12 max_gates
= "Compile using: ['h', 'cx', 'z', 'x', 'ccx', 'swap']" # model was trained with phrases like this, allow full gate set
prompt prompt
"Compile using: ['h', 'cx', 'z', 'x', 'ccx', 'swap']"
Exercise 1
Inspired from (quantumcomputing.stackexchange.com/questions/13821/generate-a-3-qubit-swap-unitary-in-terms-of-elementary-gates/13826). Note, this unitary WAS in the training set.
= np.matrix([[1,0,0,0,0,0,0,0],
U 0,1,0,0,0,0,0,0],
[0,0,1,0,0,0,0,0],
[0,0,0,0,1,0,0,0],
[0,0,0,1,0,0,0,0],
[0,0,0,0,0,1,0,0],
[0,0,0,0,0,0,1,0],
[0,0,0,0,0,0,0,1]], dtype=np.complex128)
[
assert np.allclose(U.H@U, np.identity(2**num_of_qubits)) and np.allclose(U@U.H, np.identity(2**num_of_qubits)) #check if unitary
Plot correct (exact) compiled circuits:
compile_and_plot(U, prompt)
[INFO]: (generate_comp_tensors) Generated 512 tensors
Exercise 2
Inspired from (quantumcomputing.stackexchange.com/questions/12439/procedures-and-intuition-for-designing-simple-quantum-circuits/12440). Note, this unitary WAS in the training set.
= np.matrix([[1,0,0,0,0,0,0,0],
U 0,0,0,0,0,0,0,1],
[0,1,0,0,0,0,0,0],
[0,0,1,0,0,0,0,0],
[0,0,0,1,0,0,0,0],
[0,0,0,0,1,0,0,0],
[0,0,0,0,0,1,0,0],
[0,0,0,0,0,0,1,0]], dtype=np.complex128)
[
assert np.allclose(U.H@U, np.identity(2**num_of_qubits)) and np.allclose(U@U.H, np.identity(2**num_of_qubits)) #check if unitary
Plot correct (exact) compiled circuits:
compile_and_plot(U, prompt)
[INFO]: (generate_comp_tensors) Generated 512 tensors
Exercise 3
A randomly generated unitary (from a random circuit). This unitary WAS NOT in the training set, it is new to the model!
= np.matrix([[ 0.70710678, 0. , 0. , 0. , 0.70710678, 0. , 0. , 0. ],
U 0. , -0.70710678, 0. , 0. , 0. , -0.70710678, 0. , 0. ],
[ -0.70710678, 0. , 0. , 0. , 0.70710678, 0. , 0. , 0. ],
[0. , 0.70710678, 0. , 0. , 0. , -0.70710678, 0. , 0. ],
[ 0. , 0. , 0.70710678, 0. , 0. , 0. , 0. , 0.70710678],
[ 0. , 0. , 0. , 0.70710678, 0. , 0. , 0.70710678, 0. ],
[ 0. , 0. , -0.70710678, 0. , 0. , 0. , 0. , 0.70710678],
[ 0. , 0. , 0. ,-0.70710678, 0. , 0. , 0.70710678, 0. ]], dtype=np.complex128)
[
assert np.allclose(U.H@U, np.identity(2**num_of_qubits)) and np.allclose(U@U.H, np.identity(2**num_of_qubits)) #check if unitary
Plot correct (exact) compiled circuits:
compile_and_plot(U, prompt)
[INFO]: (generate_comp_tensors) Generated 512 tensors
Transpile and discover
Set an initial circuit we want to transpile, optimize or use for discovering sub-arrangements:
= QuantumCircuit(3)
qc 2)
qc.h(0,1)
qc.cx(2,1)
qc.cx(1)
qc.h(1)
qc.x(1)
qc.h(2)
qc.x(
= qi.Operator(qc).to_matrix() # the unitary of the circuit
U
#-----------------------------------------
= qc.draw("mpl")
fig fig
We set different gate pool targets to see what the model gives us:
= f"Compile using: {[x for x in pipeline.gate_pool]}", "all"
cs_1
= "Compile using: ['h', 'cx', 'z', 'ccx']" , "no x, no swap"
cs_2 = "Compile using: ['h', 'cx', 'x', 'ccx']" , "no z, no swap"
cs_3 = "Compile using: ['h', 'x', 'ccx']" , "no cx, no z, no swap"
cs_4 = "Compile using: ['h', 'z', 'x', 'ccx']" , "no cx, no swap"
cs_5
= [cs_1, cs_2, cs_3, cs_4, cs_5]
cs cs
[("Compile using: ['h', 'cx', 'z', 'x', 'ccx', 'swap']", 'all'),
("Compile using: ['h', 'cx', 'z', 'ccx']", 'no x, no swap'),
("Compile using: ['h', 'cx', 'x', 'ccx']", 'no z, no swap'),
("Compile using: ['h', 'x', 'ccx']", 'no cx, no z, no swap'),
("Compile using: ['h', 'z', 'x', 'ccx']", 'no cx, no swap')]
= 512
samples = 3
num_of_qubits = 12 max_gates
Compile with the different gate-sets and plot correct (exact) compiled circuits. Note, some of the circuits might look the same but the gate time-sequences are distinct. Qiskit reorders “parallel” gates to make smaller plots.
for c, note in cs: compile_and_plot(U, c)
[INFO]: (generate_comp_tensors) Generated 512 tensors
[INFO]: (generate_comp_tensors) Generated 512 tensors
[INFO]: (generate_comp_tensors) Generated 512 tensors
[INFO]: (generate_comp_tensors) Generated 512 tensors
[INFO]: (generate_comp_tensors) Generated 512 tensors
import genQC
print("genQC Version", genQC.__version__)
genQC Version 0.2.0