# Gate synthesis with parametrised precision

I am wondering whether Qiskit (or other quantum program language) can perform gate synthesis with parametrised precision.

I tried with

from qiskit.transpiler.passes.synthesis import SolovayKitaev

But this package can only decompose to a sequence of Clifford+T gates.

What I am looking for is a method accepting an arbitrary gate set and an approximation parameter.

The "method accepting an arbitrary gate" is a tricky part". There are methods with some set of gates that you might have to later translate with a further transpiler pass.

For the approximation part, take a look to Approximate Quantum Compiler (AQC), included in Qiskit 0.36 (Terra 0.19, Apr 2022). I personally prefer to use it as a plugin with the UnitarySynthesis and configure it via plugin_config. In particular, you can set the optimizer (see the available ones here) and its parameters.

In the following example, consider SLSQP(maxiter). By changing maxiter you can extend the computation time in favor of more exact approximation:

1. Create a circuit with a random unitary (4 qubits in this case)
from qiskit import QuantumCircuit
from qiskit.quantum_info import Operator
from qiskit.quantum_info.random import random_unitary

qc = QuantumCircuit(4)

qc.unitary(random_unitary(16, seed=0), [0,1,2,3])
original_matrix = Operator(qc).data

1. Take UnitarySynthesis and an optimizer (SLSQP in this case)
from qiskit.transpiler.passes.synthesis.unitary_synthesis import UnitarySynthesis
from qiskit.algorithms.optimizers import SLSQP

1. Using the aqc plugin, set optimizer with different maxiter. By the way, you can put some basis in basis_gates, not an arbitrary basis tho:

a. With SLSQP(1), it takes 192ms in my laptop and the accumulated error is 78.

result = UnitarySynthesis(basis_gates=["rx", "ry", "rz", "cx"], method='aqc', plugin_config={"seed": 42, "optimizer": SLSQP(1)})(qc)

import numpy as np
approx_matrix = Operator(result).data
np.sum(np.abs(original_matrix - approx_matrix))

77.80895049882317


b. With SLSQP(10), it takes 1.55s in my laptop and the accumulated error is 36.

result = UnitarySynthesis(basis_gates=["rx", "ry", "rz", "cx"], method='aqc', plugin_config={"seed": 42, "optimizer": SLSQP(10)})(qc)
approx_matrix = Operator(result).data

np.sum(np.abs(original_matrix-approx_matrix))

35.63557540562521


c. With SLSQP(100), it takes 13.9s in my laptop and the accumulated error is 4.

result = UnitarySynthesis(basis_gates=["rx", "ry", "rz", "cx"], method='aqc', plugin_config={"seed": 42, "optimizer": SLSQP(100)})(qc)
approx_matrix = Operator(result).data

np.sum(np.abs(original_matrix-approx_matrix))

4.048035307578057


BQSKit can do this exactly. You will need to calculate the unitary of your gate, and you can just plug that into its compile function.

You can accomplish this with the following:

from bqskit import compile, MachineModel
from bqskit.ir.gates import CZGate, U3Gate # import the gates you want to use
model = MachineModel(number_qubits, gate_set={CZGate(), ...})  # Define your desired gateset
output_circuit = compile(gate_unitary_input, model, synthesis_epsilon=1e-8)


In the above code snippet, you define the desired gate set inside of a MachineModel object and then compile your gate into a circuit. The resulting solution will be at most synthesis_epsilon away from the target unitary. You may also need to set the max_synthesis_size parameter in the compile call if your input is larger than 3 qubits.

For more info, you can refer to the BQSKit documentation or tutorial.