liveBook · Manning

Cosine-sine decomposition

As explained in Quantum Programming in Depth

A cosine-sine decomposition is a matrix factorization that represents an even-dimensional unitary matrix as a product of two block-diagonal unitary matrices and a structured cosine-sine factor.

Cosine-sine decomposition factors an even-dimensional unitary matrix into two block-diagonal unitaries and a structured cosine-sine (CS) factor. For a 2M × 2M matrix, U = [[A_l, 0], [0, B_l]] [[C, −S], [S, C]] [[A_r, 0], [0, B_r]], where A_l, A_r, B_l, and B_r are M × M unitaries, while C and S are diagonal and satisfy C² + S² = I. section 3.4

The factorization turns arbitrary-unitary implementation into a sequence of simpler operations: apply the right block-diagonal unitary, the CS unitary, and the left block-diagonal unitary. Implementing the block-diagonal factors recursively uses controlled unitaries on fewer qubits. section 3.10.1 section 3.8

A practical implementation workflow is:

  • Obtain the decomposition with a classical linear-algebra routine. section 3.4
  • Extract the left and right blocks and implement each as a two-block block-diagonal unitary. section 3.10.2
  • Convert the diagonal C and S entries into CS rotation pairs and append the CS unitary. section 3.10.2
  • Test the resulting circuit against arbitrary unitaries, keeping small matrices for faster runs. section 3.10.2
Listing 3.20 Qiskit code to implement an arbitrary unitary
from scipy.linalg import cossin

def apply_arbitrary_unitary(n, u):
  if n == 1:
    return apply_one_qubit(u)
    
  circ = QuantumCircuit(n)
  left, cs, right = cossin(u, p=len(u) / 2, q=len(u) / 2)

  ar, br = extract_blocks(right)
  circ.append(apply_two_block_diagonal(n, ar, br), range(n))

  cs_pairs = []
  for i in range(len(cs) // 2):
    cs_pairs += [(cs[i][i], cs[i + len(cs) // 2][i])]
  circ.append(apply_arbitrary_cs_matrix(n, cs_pairs), range(n))

  al, bl = extract_blocks(left)
  circ.append(apply_two_block_diagonal(n, al, bl), range(n))
  return circ.decompose().to_gate()

def extract_blocks(matrix):  #1
  block_len = len(matrix)  #1
  a = [row[0 : block_len] for row in matrix[0 : block_len]]
  b = [row[block_len : ] for row in matrix[block_len : ]]
  return a, b

Use the Qiskit listing to trace the recursive base case, SciPy decomposition, block extraction, CS-pair construction, and circuit composition. section 3.10.2

Verifying a 4 × 4 factorization

A real 4 × 4 unitary can be decomposed with scipy.linalg.cossin, then reconstructed as left @ cs @ right. Checking allclose(u, left @ cs @ right) returns True, confirming the factorization numerically. section 3.4

How Quantum Programming in Depth covers it

How deep it goes
Defines the factor shapes and recursive circuit workflow but defers deriving or computing the classical decomposition by hand.
Examples
Uses a 4 × 4 real matrix, SciPy reconstruction, a block-antidiagonal special case, and Qiskit tests through four qubits.
What it assumes
Assumes single-qubit unitaries and testing, while introducing multiqubit decomposition through smaller structured matrix cases.
What you can do after
Build it from scratch in Qiskit using SciPy decomposition, recursive block implementation, CS construction, and numerical tests.
How it connects
Connects unitary matrices, block-diagonal matrices, multiplexers, controlled unitaries, quantum gates, and hybrid classical-quantum computation.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Quantum Programming in Depth. The section links point to the parts of the book it draws on.