A block-diagonal unitary has the form U = [[A, 0], [0, B]], where A and B are independent unitary matrices of equal size. For n qubits, U is 2^n × 2^n, and each block is 2^(n−1) × 2^(n−1). section 3.8
Interpret this structure as a quantum conditional: one control qubit selects whether A or B acts on the remaining qubits. This is the one-condition-qubit case of a multiplexer, while a diagonal unitary is the extreme case in which every qubit supplies a condition. section 3.8
Listing 3.9 Qiskit code for a two-qubit block-diagonal unitary
def apply_two_qubit_block_diagonal(a, b): circ = QuantumCircuit(2) circ.append(apply_one_qubit(b).control(1), [1, 0]) circ.append(apply_one_qubit(a).control(1, ctrl_state=0), [1, 0]) return circ
Implement the two-qubit case by controlling B on the most significant qubit and A when that qubit is in state 0; the qubit-index order matters. section 3.5.3
A practical implementation and verification workflow is:
- Reuse a one-qubit unitary operation as the building block for each diagonal block. section 3.5.3
- Detect zero off-diagonal blocks, extract the top-left and bottom-right matrices, and dispatch to the block-diagonal operation. section 3.8.2
- Construct the complete matrix from A and B, compare it with the simulated operator, and test randomized unitary inputs. section 3.5.3
- Limit tests to implemented cases when the arbitrary-unitary helper remains a placeholder; otherwise, recursive cosine-sine decomposition supports arbitrary sizes. section 3.8.2 section 3.10.2
Listing 3.16 Qiskit code for a two-block block-diagonal unitary
def apply_arbitrary_unitary(n, u): #1
if n == 1:
return apply_one_qubit(u) #2
if n == 2:
if all(isclose(v, 0) for v in #3
u[0][2:4] + u[1][2:4] + #3
u[2][0:2] + u[3][0:2]): #3
tl = [u[0][0:2], u[1][0:2]] #3
br = [u[2][2:4], u[3][2:4]] #3
return apply_two_qubit_block_diagonal(tl, br).decompose().to_gate()
if all(isclose(v, 0) for v in #4
u[0][0:2] + u[1][0:2] + #4
u[2][2:4] + u[3][2:4]): #4
tr = [u[0][2:4], u[1][2:4]] #4
bl = [u[2][0:2], u[3][0:2]] #4
return apply_two_qubit_block_antidiagonal(tr, bl).decompose().to_gate()
raise NotImplementedError("The case of " +
"arbitrary 2-qubit unitaries is not implemented yet")
raise NotImplementedError(
"The case of 3+-qubit unitaries is not implemented yet")
def apply_two_block_diagonal(n, a, b):
circ = QuantumCircuit(n)
circ.append(apply_arbitrary_unitary(n - 1, b).control(1), #5
[n - 1] + list(range(n - 1)))
circ.append(apply_arbitrary_unitary(n - 1, a).control(1, ctrl_state=0),
[n - 1] + list(range(n - 1)))
return circ
The Qiskit helper demonstrates dispatch from matrix structure to the two-qubit block-diagonal implementation, with explicit failure for unsupported sizes or general cases. section 3.8.2
At full generality, block-diagonal unitaries become recursive components of arbitrary-unitary synthesis. A cosine-sine decomposition supplies left and right block-diagonal factors surrounding a CS matrix; Qiskit and Q# illustrate the same decomposition-oriented logic. section 3.8.3 section 3.10.2
