Z-register Operations#
Operations on the Z-register allow users to manipulate complex-valued variables inside a quantum circuit. This section covers all the information needed to perform operations on the complex-valued variables stored in the Z-register.
What is the Z-register?#
In MIMIQ, complex-valued variables are stored in a special register called the Z-register. Practically speaking, the Z-register is just a vector of complex numbers (Complex values) that can be manipulated with circuit operations, similarly to the quantum register (qubits) or the classical register (bits).
When visualizing a circuit, Z-register indices are represented with the letter z
.
What can you do with the Z-register?#
Operations involving the Z-register can be broadly divided into two types:
Operations that store information into the Z-register.
Operations that manipulate information already stored in the Z-register.
Storing information in the Z-register#
Some operations compute values from the quantum state and store the result into the Z-register:
ExpectationValue
: Computes the expectation value of an observable.Amplitude
: Stores the complex amplitude of a bitstring.
>>> c = Circuit()
>>> c.push(GateH(), 1)
2-qubit circuit with 1 instructions:
└── H @ q[1]
>>> c.push(GateCX(), 1, 2)
3-qubit circuit with 2 instructions:
├── H @ q[1]
└── CX @ q[1], q[2]
>>> c.push(ExpectationValue(PauliString("XX")), 1, 2, 1) # qubits 1,2 -> z[1]
3-qubit circuit with 3 instructions:
├── H @ q[1]
├── CX @ q[1], q[2]
└── ⟨XX⟩ @ q[1,2], z[1]
>>> c.push(ExpectationValue(PauliString("ZZ")), 1, 2, 2) # qubits 1,2 -> z[2]
3-qubit circuit with 4 instructions:
├── H @ q[1]
├── CX @ q[1], q[2]
├── ⟨XX⟩ @ q[1,2], z[1]
└── ⟨ZZ⟩ @ q[1,2], z[2]
>>> c.push(Amplitude(BitString(2)), 3) # bitstring -> z[3]
3-qubit circuit with 5 instructions:
├── H @ q[1]
├── CX @ q[1], q[2]
├── ⟨XX⟩ @ q[1,2], z[1]
├── ⟨ZZ⟩ @ q[1,2], z[2]
└── Amplitude(bs"00") @ z[3]
Manipulating the Z-register#
The following operations perform arithmetic on Z-register variables:
Add
: Adds Z-register variables.Multiply
: Multiplies Z-register variables.Pow
: Raises a Z-register variable to a power.
>>> c.push(Add(3), 4, 1, 2) # z[4] = z[1] + z[2]
3-qubit circuit with 6 instructions:
├── H @ q[1]
├── CX @ q[1], q[2]
├── ⟨XX⟩ @ q[1,2], z[1]
├── ⟨ZZ⟩ @ q[1,2], z[2]
├── Amplitude(bs"00") @ z[3]
└── z[4] += 0.0 + z[1] + z[2]
>>> c.push(Multiply(3), 5, 1, 2) # z[5] = z[1] * z[2]
3-qubit circuit with 7 instructions:
├── H @ q[1]
├── CX @ q[1], q[2]
├── ⟨XX⟩ @ q[1,2], z[1]
├── ⟨ZZ⟩ @ q[1,2], z[2]
├── Amplitude(bs"00") @ z[3]
├── z[4] += 0.0 + z[1] + z[2]
└── z[5] *= 1.0 * z[1] * z[2]
>>> c.push(Multiply(1, 0.2), 3) # z[3] *= 0.2
3-qubit circuit with 8 instructions:
├── H @ q[1]
├── CX @ q[1], q[2]
├── ⟨XX⟩ @ q[1,2], z[1]
├── ⟨ZZ⟩ @ q[1,2], z[2]
├── Amplitude(bs"00") @ z[3]
├── z[4] += 0.0 + z[1] + z[2]
├── z[5] *= 1.0 * z[1] * z[2]
└── z[3] *= 0.2
>>> c.push(Pow(-1), 3) # z[3] = z[3] ** -1
3-qubit circuit with 9 instructions:
├── H @ q[1]
├── CX @ q[1], q[2]
├── ⟨XX⟩ @ q[1,2], z[1]
├── ⟨ZZ⟩ @ q[1,2], z[2]
├── Amplitude(bs"00") @ z[3]
├── z[4] += 0.0 + z[1] + z[2]
├── z[5] *= 1.0 * z[1] * z[2]
├── z[3] *= 0.2
└── z[3] = z[3]^(-1)
Additionally, a constant value can be added or multiplied in. For instance, Add(2, 0.5)
adds 0.5 to both inputs before summing them.
Example: Ising Hamiltonian Expectation Value (Energy)#
A more complex example is the computation of the expectation value of a 1D Ising Hamiltonian with transverse field:
where ( sigma^z_j ) and ( sigma^x_j ) are Pauli matrices on the ( j )-th qubit, ( J ) is the coupling constant, and ( h ) is the transverse magnetic field.
The circuit to compute the expectation value is:
Example: Ising Hamiltonian Expectation Value (Energy)
A more complex example is the computation of the expectation value of a 1D Ising Hamiltonian with transverse field:
where ( sigma^z_j ) and ( sigma^x_j ) are Pauli matrices on the ( j )-th qubit, ( J ) is the coupling constant, and ( h ) is the transverse magnetic field.
The circuit to compute the expectation value is:
>>> from mimiqcircuits import *
>>> N = 4 # number of qubits
>>> J = 1.0 # coupling strength
>>> h = 0.5 # transverse field
>>> c = Circuit()
>>> for j in range(N - 1):
... newz = c.num_zvars()
... _ = c.push(ExpectationValue(PauliString("ZZ")), j, j + 1, newz)
... _ = c.push(Multiply(1, -J), newz)
>>> c
4-qubit circuit with 6 instructions:
├── ⟨ZZ⟩ @ q[0,1], z[0]
├── z[0] *= -1.0
├── ⟨ZZ⟩ @ q[1,2], z[1]
├── z[1] *= -1.0
├── ⟨ZZ⟩ @ q[2,3], z[2]
└── z[2] *= -1.0
>>> for j in range(N):
... newz = c.num_zvars()
... _ = c.push(ExpectationValue(GateX()), j, newz)
... _ = c.push(Multiply(1, -h), newz)
>>> c
4-qubit circuit with 14 instructions:
├── ⟨ZZ⟩ @ q[0,1], z[0]
├── z[0] *= -1.0
├── ⟨ZZ⟩ @ q[1,2], z[1]
├── z[1] *= -1.0
├── ⟨ZZ⟩ @ q[2,3], z[2]
├── z[2] *= -1.0
├── ⟨X⟩ @ q[0], z[3]
├── z[3] *= -0.5
├── ⟨X⟩ @ q[1], z[4]
├── z[4] *= -0.5
├── ⟨X⟩ @ q[2], z[5]
├── z[5] *= -0.5
├── ⟨X⟩ @ q[3], z[6]
└── z[6] *= -0.5
>>> total_terms = c.num_zvars()
>>> _ = c.push(Add(total_terms), *range(total_terms))
>>> c
4-qubit circuit with 15 instructions:
├── ⟨ZZ⟩ @ q[0,1], z[0]
├── z[0] *= -1.0
├── ⟨ZZ⟩ @ q[1,2], z[1]
├── z[1] *= -1.0
├── ⟨ZZ⟩ @ q[2,3], z[2]
├── z[2] *= -1.0
├── ⟨X⟩ @ q[0], z[3]
├── z[3] *= -0.5
├── ⟨X⟩ @ q[1], z[4]
├── z[4] *= -0.5
├── ⟨X⟩ @ q[2], z[5]
├── z[5] *= -0.5
├── ⟨X⟩ @ q[3], z[6]
├── z[6] *= -0.5
└── z[0] += 0.0 + z[1] + z[2] + z[3] + z[4] + z[5] + z[6]