# Unitary Gate¶

Given a $$2^{n}\times2^{n}$$ unitary matrix, the unitary-gate function constructs an equivalent unitary gate which acts on $$n$$ qubits accordingly. For $$n>2$$, the synthesis process implementation is based on [1].

Optionally, the user can specify the gate to be a controlled gate, such that it is applied if and only if the logical AND of all control qubits is satisfied. The user can also specify the control state, using an integer or a bit string representations. If no control state is specified, it will be set to $$2^{n_{c}}-1$$, where $$n_{c}$$ is the number of control qubits.

## Syntax¶

Function: UnitaryGate

Parameters:

• data: DataArray
• num_ctrl_qubits: Optional[PositiveInt]
• ctrl_state: Optional[Union[str, PositiveOrZeroInt]]
{
"function": "UnitaryGate",
"function_params": {
"data": [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 0, "-1j"],
[0, 0, 0, "1j"]
],
"num_ctrl_qubits": 3,
"ctrl_state": "011"
}
}


## Example 1: Uncontrolled Unitary Gate¶

The following example shows a $$2$$-qubit unitary gate generated from a series of arbitrary $$2$$-rotations in the formed $$4$$ dimensional space. The SDK code includes the construction of the matrix using NumPy functions, while the textual model is created by feeding the explicit numerical values. Here, we don't specify the number of control qubits, therefore the gate is not controlled.

{
"constraints": {
"max_width": 2,
"max_depth": 10
},
"logic_flow": [
{
"function": "UnitaryGate",
"function_params": {
"data": [
[
"(-0.437588928003977-0.22742705999986979j)",
"(0.5162471759231342+0.5412974930115615j)",
"(-0.0257562186040025-0.09612351644098814j)",
"(0.11352149651283158+0.41771820225920475j)"
],
[
"(-0.4657883653698742-0.4426774493996494j)",
"(-0.3564263367893152-0.37953725275954014j)",
"(-2.7383934913210134e-17+0.447213595499958j)",
"(0.24082111067114909+0.2408211106711492j)"
],
[
"(0.276003031585611-0.227055039284318j)",
"(0.24522446154306524-0.17374499218631134j)",
"(0.8720020339202011+0j)",
"(0.08819092511956897+0.11739161131796567j)"
],
[
"(0.08990251208306779-0.4561322296873242j)",
"(0.1573622060431408+0.23305019820160192j)",
"(-0.08618198713953264+0.14927158042291808j)",
"(0.38451638587720915-0.7261015617334183j)"
]
]
}
}
]
}

import numpy as np
from classiq import ModelDesigner
from classiq.builtin_functions import UnitaryGate

num_gate_qubits = 2
thetas = [np.pi / 3, np.pi / 7, np.arccos(3 / 5), 0.2, np.pi / 4, np.pi / 2]
phis = [np.pi / 4, np.pi / 3, np.pi / 2, np.pi / 5, np.pi / 4, np.pi / 3]
pairs = [[0, 3], [3, 2], [1, 2], [0, 1], [1, 3], [0, 1]]
gate_matrix = np.identity(2**num_gate_qubits, dtype=complex)
for theta, phi, index in zip(thetas, phis, pairs):
data_next = np.identity(2**num_gate_qubits, dtype=complex)
data_next[index[0], index[0]] = 0
data_next[index[1], index[1]] = 0
data_next[np.ix_(index, index)] = [
[
np.exp(-1j * phi) * np.cos(theta / 2),
-np.exp(-1j * phi) * np.sin(theta / 2),
],
[np.sin(theta / 2), np.cos(theta / 2)],
]
gate_matrix = np.dot(gate_matrix, data_next)
gate_matrix = gate_matrix.tolist()

QUBIT_COUNT = 2
MAX_DEPTH = 10

model_designer = ModelDesigner()
unitary_gate_params = UnitaryGate(data=gate_matrix)
model_designer.UnitaryGate(unitary_gate_params)

circuit = model_designer.synthesize()
circuit.show()


The figure below shows the constituents of the gate:

## Example 2: Connected Unitary Gates¶

The following example demonstrates two $$2$$-qubit gates, connected serially. The first gate is uncontrolled. The second gate is a $$2$$-controlled $$2$$-qubit gate, switched on when the input control qubits are at state 01.

The gates are connected such that the $$2$$ qubits which are the output of the first gate serve as an input control qubits to the second gate, i.e., if the output state of the first gate is 01, the second gate will be switched on. The remaining $$2$$ qubits are the target qubits of the second gate.

The gates are connected via the input / output interface of the circuit generator. Input and output states of uncontrolled gates are defined by IN and OUT, respectively. For controlled gates, the control and target qubits are separated - input qubits are denoted by CTRL_IN, TARGET_IN, respectively, and, similarly, output qubits are denoted by CTRL_OUT, TARGET_OUT

{
"constraints": {
"max_width": 4,
"max_depth": 200
},
"logic_flow": [
{
"function": "UnitaryGate",
"function_params": {
"data": [
[
"0",
"1",
"0",
"0"
],
[
"1",
"0",
"0",
"0"
],
[
"0",
"0",
"0",
"1"
],
[
"0",
"0",
"1",
"0"
]
]
},
"outputs": "u1_out"
},
{
"function": "UnitaryGate",
"function_params": {
"data": [
[
"0j",
"0j",
"0j",
"-1j"
],
[
"0j",
"0j",
"-1j",
"0j"
],
[
"0j",
"1j",
"0j",
"0j"
],
[
"1j",
"0j",
"0j",
"0j"
]
],
"num_ctrl_qubits": 2,
"ctrl_state": "01"
},
"inputs": {
"CTRL_IN": "u1_out"
}
}
]
}

import numpy as np
from classiq import ModelDesigner, QReg
from classiq.builtin_functions import UnitaryGate

gate_matrix_1 = np.kron([[1, 0], [0, 1]], [[0, 1], [1, 0]])
gate_matrix_2 = np.kron([[0, -1j], [1j, 0]], [[0, 1], [1, 0]])
gate_matrix_1 = gate_matrix_1.tolist()
gate_matrix_2 = gate_matrix_2.tolist()

QUBIT_COUNT = 4
MAX_DEPTH = 200

model_designer = ModelDesigner()
x = QReg(size=len(gate_matrix_1))

unitary_gate_1_params = UnitaryGate(data=gate_matrix_1)
model_designer.UnitaryGate(unitary_gate_1_params, out_wires=x)

unitary_gate_2_params = UnitaryGate(
data=gate_matrix_2, num_ctrl_qubits=2, ctrl_state="01"
)
model_designer.UnitaryGate(unitary_gate_2_params, in_wires={"CTRL_IN": x})

circuit = model_designer.synthesize()
circuit.show()


As expected, the resulting circuit is more complex:

## References¶

[1] R. Iten et al, Quantum Circuits for Isometries ,Phys. Rev. A 93 (2016). https://link.aps.org/doi/10.1103/PhysRevA.93.032318