Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Statevector.from_instruction to mirror Operator.from_instruction #13621

Open
rht opened this issue Jan 7, 2025 · 6 comments
Open

Add Statevector.from_instruction to mirror Operator.from_instruction #13621

rht opened this issue Jan 7, 2025 · 6 comments
Labels
mod: quantum info Related to the Quantum Info module (States & Operators) type: feature request New feature or request

Comments

@rht
Copy link
Contributor

rht commented Jan 7, 2025

Environment

  • Qiskit version: 1.3.1
  • Python version: 3.12.8
  • Operating system: NixOS 25.05

Also relevant:

  • NumPy version: 2.2.1
  • qiskit-aer version: 0.15.1

What is happening?

The error comes from a randomly generated circuit in our repo when we tried to bump to Qiskit 1.3.1. It appears that bumping NumPy from 1.x to 2.0 had resulted in a different random circuit in our test suite (even though they had been made deterministic via pytest seeds). I tried downgrading all the way to Qiskit 1.1.2, and the statevector discrepancy still happens.

How can we reproduce the issue?

import numpy as np
from qiskit import transpile
from qiskit.qasm3 import loads
from qiskit.quantum_info import Statevector
from qiskit_aer import Aer

# This is a randomly generated circuit
qasm = """
OPENQASM 3.0;
include "stdgates.inc";
qubit[5] q;
swap q[2], q[1];
swap q[3], q[4];
swap q[4], q[1];
h q[2];
z q[2];
cx q[3], q[1];
cx q[3], q[4];
h q[3];
cx q[4], q[3];
s q[3];
cx q[4], q[3];
sdg q[3];
h q[3];
cx q[3], q[4];
t q[0];
s q[2];
s q[0];
x q[4];
"""

c = loads(qasm)
backend = Aer.get_backend("aer_simulator")

transpiled1 = transpile(c, backend, optimization_level=0)
transpiled2 = transpile(c, backend, optimization_level=2)

sv1 = Statevector(transpiled1)
sv2 = Statevector(transpiled2)
for i, e in enumerate(sv1):
    if not np.isclose(e, sv2[i]):
        print(i, e, sv2[i])

What should happen?

The statevectors should have been equivalent up to a global phase, but they aren't.

Any suggestions?

No response

@rht rht added the bug Something isn't working label Jan 7, 2025
@rht
Copy link
Contributor Author

rht commented Jan 7, 2025

The output of the code above:

4 0j (0.7071067811865477+0j)
6 0j (4.329780281177467e-17-0.7071067811865476j)
16 (0.7071067811865476-4.329780281177466e-17j) (3.016815636692419e-17+0j)
20 (-4.3297802811774646e-17-0.7071067811865475j) 0j

TwoFour bitstrings are swapped.

@jakelishman
Copy link
Member

This is expected behaviour - we perform the swaps virtually as part of the transpilation at higher optimisation levels, and this is reflected in the output layout of the circuit. See more discussion in #12440, for example.

@rht
Copy link
Contributor Author

rht commented Jan 7, 2025

Thank you for the answer. Does that mean if I want to get a consistent vector numerics for any arbitrary optimization levels, instead of doing

sv = Statevector(circuit)

I do

sv2 = Statevector(QuantumCircuit(transpiled2.num_qubits))
transpiled2_unitary = Operator.from_circuit(transpiled2)
sv2 = sv2.evolve(transpiled2_unitary)

?
I can use this just fine in my case, where it is a backend ccode, but I find it a surprising gotcha for end users nonetheless.

@jakelishman
Copy link
Member

If we don't already have statevector.from_instruction analagous to Operator.from_instruction, we should probably add it to simplify the complexity of this use case.

In general, compilation involves changing the Hilbert space the circuit is defined in - typically we go from virtual to physical qubits. The two spaces might be isomorphic, but they don't necessarily need to be, and to satisfy hardware coupling constraints, even in the isomorphic case we have to change the bimap over the course of circuit execution. In this case, you're trying to think about the Hilbert spaces as being unchanged, but that's not how we approach compilation - the use of compiled output in simulators is a side-effect for us, where the first priority is optimising the output quality for hardware execution. Hopefully a Statevector.from_circuit method would be a fair compromise for you, though.

Fwiw, I think setting routing_method="none" disables the swap elision as a side effect (at the cost of circuit quality, in this case), but I might be misremembering.

@rht
Copy link
Contributor Author

rht commented Jan 7, 2025

I see your point regarding prioritizing for the hardware execution over the consistency of the qubit labels in the abstract layout. But I often use the Statevector(circuit) for inspecting the circuit, and by default, I'd expect the bitstring labels to match the output if I were to run the given circuit on a quantum hardware. Why shouldn't Statevector(circuit) by default automatically check the circuit.layout and then apply the permutations?

However, I'm fine with a Statevector.from_circuit that does the circuit layout remapping by default, for daily use.

Fwiw, I think setting routing_method="none" disables the swap elision as a side effect (at the cost of circuit quality, in this case), but I might be misremembering.

I tested this, and it worked. It sure is simpler.

@jakelishman
Copy link
Member

I'd expect the bitstring labels to match the output if I were to run the given circuit on a quantum hardware.

This expectation is making a tacit assumption that qubit 0 is measured into clbit 0, etc - there wouldn't be a bitstring at all without a measurement. If we don't have clbits and measurements to fix the observability, it comes down to my Hilbert space problem again. There still is a proper isomorphism between the input virtual qubits and the output qubits in this case (though in general those two spaces don't need to be isomorphic - for example, we could reuse the same physical qubit for two different virtual qubits if the state lifetimes don't overlap), but we don't require that the implicit qubits indices are equal between those two things.

Let me tag this as a feature request to add Statevector.from_circuit to mirror Operator.from_circuit, so there's an easy path to do what you want - I agree it's a very valid thing to want to do at small scales, and you shouldn't have to compromise the transpile to do it.

@jakelishman jakelishman added type: feature request New feature or request mod: quantum info Related to the Quantum Info module (States & Operators) and removed bug Something isn't working labels Jan 7, 2025
@jakelishman jakelishman changed the title transpile with optimization_level=2 results in a different statevector from optimization_level=0 Add Statevector.from_instruction to mirror Operator.from_instruction Jan 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
mod: quantum info Related to the Quantum Info module (States & Operators) type: feature request New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants