I am working on an implementation of the RQAOA algorithm on the Maxcut problem in Cirq. My graph G has n vertices. And after running a QAOA circuit with n qubits I obtain a state gammabeta (a vertical vector with 2^n entries).

I want to calculate M(e) = <gammabeta|Z(e)|gammabeta> for all edges e of G.

I want Z(e) to be an observable of the form $I\otimes \dots \otimes I \otimes Z_i \otimes I \otimes \dots \otimes I \otimes Z_j \otimes I \otimes \dots \otimes I$ (two dimensional $I$'s), with $i -1$ $I$'s in front if $Z_i$, $j -i -1$ in the middle and $n - j - 1$ behind $Z_j$.

That way I can use Z(e).expectation_from_state_vector(gammabeta, qubit_map=qubit_map)) to calculate M(e)

The problem I ran into is that I do not know how to do $I \otimes Z$ in cirq. If I write cirq.Z * cirq.Z I get $Z\otimes Z$, but if I do cirq.I * cirq.Z it does the regular product, and I get $Z$ back.

Does any one know how to make observable Z(e)? Or altenatively how to compute the expectation value of a circuit B with state vector a: <a|B|a>? Building the Z(e) circuit is easier, but I don't know how to calculate the expectation value if a state vector from one circuit, on another circuit.

  • $\begingroup$ If you're using my answer to your previous question quantumcomputing.stackexchange.com/questions/16518/… then you don't need to specify the identity gates. They are implicit when you declare something like cirq.Z(qi) and then take its expectation value with respect to a fixed qubit map over all your computational qubits. The role of that qubit map is precisely to pad $Z_i$ into $I \otimes I \otimes \dots \otimes I \otimes Z_i \otimes I \dots$ before computing $\langle Z_i \rangle$ $\endgroup$ – forky40 Mar 17 at 20:51

You can use a cirq.DensePauliString to represent a tensor product of Paulis:

gate = cirq.DensePauliString("IIZZIZ")
a, b, c, d, e, f = cirq.LineQubit.range(6)
op1 = gate(a, b, c, d, e, f)

It can be a bit tedious to specify all those identities, and to manage making sure you pass the right qubit into the right position, so I'd recommend directly using the sparse cirq.PauliString instead (it's what you get anyways when you apply the dense gate to qubits):

op2 = cirq.PauliString({c: "Z", d: "Z", f: "Z"})
assert op1 == op2

Or, as a shorthand, you can create the sparse Pauli string by just multiplying Pauli terms together:

op3 = cirq.Z(c) * cirq.Z(d) * cirq.Z(f)
assert op1 == op3

Once you have a cirq.PauliString you can do lots of useful stuff. Multiply it, raise it to a power, exponentiate it, create linear combinations, conjugate by Cliffords, etc, etc. And although I'm not sure what the latest greatest way to get expectation values is in cirq, I'd bet that method takes Pauli strings as an argument.

Measuring a Pauli string can be done in a variety of ways. The easiest to program (and hardest to run on hardware) is with phase kickback onto a clean ancilla qubit:

from typing import list

def pauli_string_measurement(observable: cirq.PauliString, ancilla: cirq.Qid, key: str) -> List[cirq.Operation]:
    return [
        cirq.measure(ancilla, key=key),
    observable=cirq.Z(c) * cirq.Z(d) * cirq.Z(f),
# 2: ───────────PauliString(+Z)──────────────────
#               │
# 3: ───────────Z────────────────────────────────
#               │
# 5: ───────────Z────────────────────────────────
#               │
# extra: ───H───@─────────────────H───M('out')───

The easiest to run on hardware is to measure each qubit in the observable in the appropriate basis, and then xor them together:

op = cirq.X(a) * cirq.Y(b) * cirq.Z(c)
    cirq.measure(*op.qubits, key="things_to_xor"),
# 0: ───Y^-0.5───M('things_to_xor')───
#                │
# 1: ───X^0.5────M────────────────────
#                │
# 2: ───I────────M────────────────────

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.