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

After applying simplifications the unitary matrices change. #195

Closed
rcasjimenez opened this issue Feb 18, 2024 · 10 comments
Closed

After applying simplifications the unitary matrices change. #195

rcasjimenez opened this issue Feb 18, 2024 · 10 comments
Labels
bug Something isn't working

Comments

@rcasjimenez
Copy link

rcasjimenez commented Feb 18, 2024

I'm trying out some of the simplifications of pyzx, applying them to the pyzx graph coming from a Qiskit circuit.
To check that pyzx has not altered the semantics of the circuit, I check whether the unitary matrix of the original circuit and that of the resulting circuit change.
I have used the same circuit example as in https://github.com/Quantomatic/pyzx/blob/a6e34d90b62ecb1f42b1d7378c4fa65b73005880/tests/test_qasm.py#L214 and I have applied different simplifications.

Below, I indicate two different tests carried out (2 different executions).
In both, I replaced https://github.com/Quantomatic/pyzx/blob/a6e34d90b62ecb1f42b1d7378c4fa65b73005880/tests/test_qasm.py#L242 by:

Simplification test 1

to_gh(g)
pivot_simp(g)

Simplification test 2

to_gh(g)
match_spider = zx.rules.match_spider_parallel(g.copy())[0]
zx.rules.apply_rule(g, zx.rules.spider, [match_spider])

Failing https://github.com/Quantomatic/pyzx/blob/a6e34d90b62ecb1f42b1d7378c4fa65b73005880/tests/test_qasm.py#L248

in both tests.

I´m using pyzx 0.8.0 and Qiskit 0.46.0 to execute the unittest.
In addition, I created a notebook and I retrieved the same results using Qiskit 1.0 and qasm v2 exporters/importers from Qiskit.

Is it a bug or is the way I apply the simplifications incorrect?
I am very confused with the results obtained because keep circuit semantics is a fundamental feature of ZX-Calculus simplifications.

Note: I assumed that I should run to_gh(g) before both simplifications but even without running it, both tests are incorrect.

@jvdwetering
Copy link
Collaborator

When you do g.copy() the labels of the vertices can change. The match you get out of this is hence not necessarily valid when you apply it to g itself. Can you try again without the g.copy()?

@rcasjimenez
Copy link
Author

rcasjimenez commented Feb 20, 2024

Hi @jvdwetering,

I tried using 'g' instead of 'g.copy()', but the result was the same.

After doing several tests, I think the error is due to 'extract_circuit' method.
In the following simple example you can see that the unitary matrices are not equivalent after extracting the circuit (I´m not using any simplification transformation/method).

In this example I start from a very simple circuit, convert it to the equivalent graph, extract a circuit from the graph and compare the unitary matrices.

Example without any simplification process

qc = QuantumCircuit(2)       # original circuit
qc.h(1)
qc.h(0)
qc.cz(0, 1)

qc1 = transpile(qc)
t1 = quantum_info.Operator(qc1).data

c = Circuit.from_qasm(qc1.qasm())
g = c.to_graph()
print(c)                                  # print the circuit architecture 
print(qc1.qasm())
print(qc)
print(t1)

print('-----------')
# Extracting a circuit
qasm = extract_circuit(g.copy()).to_basic_gates().to_qasm()   
qc2 = QuantumCircuit().from_qasm_str(qasm)
t2 = quantum_info.Operator(qc2).data
print(extract_circuit(g.copy()))      # print the circuit architecture 
print(qasm)
print(qc2)
print(t2)

print(compare_tensors(t1, t2))     # unitary matrices
print(compare_tensors(c, extract_circuit(g.copy()).to_basic_gates()))  # circuits

Output:

Circuit(2 qubits, 0 bits, 3 gates)
OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
h q[0];
h q[1];
cz q[0],q[1];


     ┌───┐   
q_0: ┤ H ├─■─
     ├───┤ │ 
q_1: ┤ H ├─■─
     └───┘
[[ 0.5+0.j  0.5+0.j  0.5+0.j  0.5+0.j]
 [ 0.5+0.j -0.5+0.j  0.5+0.j -0.5+0.j]
 [ 0.5+0.j  0.5+0.j -0.5+0.j -0.5+0.j]
 [-0.5+0.j  0.5+0.j  0.5+0.j -0.5+0.j]]
-----------
Circuit(2 qubits, 0 bits, 5 gates) 
OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
h q[1];
h q[0];
h q[1];
h q[0];
cz q[0], q[1];

     ┌───┐┌───┐
q_0: ┤ H ├┤ H ├─■─
     ├───┤├───┤ │
q_1: ┤ H ├┤ H ├─■─
     └───┘└───┘
[[ 1.+0.j  0.+0.j  0.+0.j  0.+0.j]
 [ 0.+0.j  1.+0.j  0.+0.j  0.+0.j]
 [ 0.+0.j  0.+0.j  1.+0.j  0.+0.j]
 [ 0.+0.j  0.+0.j  0.+0.j -1.+0.j]]

False
False

@jvdwetering
Copy link
Collaborator

Ah, I see what is going wrong. You are calling extract_circuit on a graph that is not in graph-like form, meaning that not all spiders that can be fused are fused. This is an assumption that extract_circuit makes, and I guess in this case it fails silently. It is interpreting the regular edges present in the graph as Hadamard edges.

The docstring of extract_circuit says that "Given a graph put into semi-normal form by :func:~pyzx.simplify.full_reduce,
it extracts its equivalent set of gates into an instance of :class:~pyzx.circuit.Circuit.", so when it is not produced by this function the behaviour is ill-defined. Although this should be let known with an error message rather than failing silently.

@rcasjimenez
Copy link
Author

Thank you for your reply and your explanation.

Taking into account what you have told me, I have modified the test to use the two functions present in simplify.py ("zx.simplify.is_graph_like(g) and zx.simplify.to_graph_like(g)) to check the graph and to convert it to the 'graph-like form'.

Despite this, I do not get the expected result, even though a second call to 'zx.simplify.is_graph_like' returns True.

qc = QuantumCircuit(2)  # original circuit
qc.h(1)
qc.h(0)
qc.cz(0, 1)

qc1 = transpile(qc)
t1 = quantum_info.Operator(qc1).data

c = Circuit.from_qasm(qc1.qasm())
g = c.to_graph()
print(c)  # print the circuit architecture
print(qc1.qasm())
print(qc)
print(t1)

print('-----------')
print(f"zx.simplify.is_graph_like(g):{zx.simplify.is_graph_like(g)}")      # check 1
zx.simplify.to_graph_like(g)                                                                # to graph-like form
print(f"zx.simplify.is_graph_like(g):{zx.simplify.is_graph_like(g)}")      # check 2
print('-----------')

#Extracting a circuit
 qasm = extract_circuit(g.copy()).to_basic_gates().to_qasm()
 qc2 = QuantumCircuit().from_qasm_str(qasm)
 t2 = quantum_info.Operator(qc2).data
 print(extract_circuit(g.copy()))  # print the circuit architecture
 print(qasm)
 print(qc2)
 print(t2)

 print(compare_tensors(t1, t2))  # unitary matrices
 print(compare_tensors(c, extract_circuit(g.copy()).to_basic_gates()))  # circuits

OUTPUT

Circuit(2 qubits, 0 bits, 3 gates)
OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
h q[0];
h q[1];
cz q[0],q[1];

     ┌───┐   
q_0: ┤ H ├─■─
     ├───┤ │
q_1: ┤ H ├─■─
     └───┘
[[ 0.5+0.j  0.5+0.j  0.5+0.j  0.5+0.j]
 [ 0.5+0.j -0.5+0.j  0.5+0.j -0.5+0.j]
 [ 0.5+0.j  0.5+0.j -0.5+0.j -0.5+0.j]
 [-0.5+0.j  0.5+0.j  0.5+0.j -0.5+0.j]]
-----------
zx.simplify.is_graph_like(g):False
zx.simplify.is_graph_like(g):True
-----------
Circuit(2 qubits, 0 bits, 5 gates)
OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
h q[1];
h q[0];
h q[1];
h q[0];
cz q[0], q[1];

     ┌───┐┌───┐
q_0: ┤ H ├┤ H ├─■─
     ├───┤├───┤ │
q_1: ┤ H ├┤ H ├─■─
     └───┘└───┘
[[ 1.+0.j  0.+0.j  0.+0.j  0.+0.j]
 [ 0.+0.j  1.+0.j  0.+0.j  0.+0.j]
 [ 0.+0.j  0.+0.j  1.+0.j  0.+0.j]
 [ 0.+0.j  0.+0.j  0.+0.j -1.+0.j]]
False
False

Is the result I get when invoking these two new functions what I expected?

Sorry, I don't know if I'm making other incorrect assumptions that avoid getting matrix equality.

@jvdwetering
Copy link
Collaborator

The function to_graph_like appears to be bugged. In your case just calling zx.to_gh(g);zx.spider_simp(g) should be enough instead.

jvdwetering added a commit that referenced this issue Feb 22, 2024
@jvdwetering
Copy link
Collaborator

The new commit I just pushed should fix this issue.

@rcasjimenez
Copy link
Author

rcasjimenez commented Feb 22, 2024

Thank you again for your reply and the update :)

  • Yes, it´s working with this update, the result of zx.simplify.is_graph_like(g) is rigth ( as above, the first one is False and the second one is True) and the matrices are the same.

  • One more question related to what you suggested regarding using the functions zx.to_gh(g);zx.spider_simp(g).
    I have executed both and they do not seem to generate a 'graph-like form', since the function that does the check (zx.simplify.is_graph_like(g)) returns False (before and after simplifications) although I got the right final result (original matrix).
    In the following execution, is the second result of the function zx.simplify.is_graph_like(g) wrong? Or is it possible that final circuits/matrices can be extracted that match the original ones even though the simplified graph is not a 'graph-like form' despite the initial requirement of the 'extract_circuit' function?
    You can check what I'm talking about at:

qc = QuantumCircuit(2)  # original circuit
qc.h(1)
qc.h(0)
qc.cz(0, 1)

qc1 = transpile(qc)
t1 = quantum_info.Operator(qc1).data

c = Circuit.from_qasm(qc1.qasm())
g = c.to_graph()
print(c)  # print the circuit architecture
print(qc1.qasm())
print(qc)
print(t1)
print('-----------')
print(f"zx.simplify.is_graph_like(g):{zx.simplify.is_graph_like(g)}")
to_gh(g)
zx.simplify.spider_simp(g, quiet=True)
print(f"zx.simplify.is_graph_like(g):{zx.simplify.is_graph_like(g)}")
print('-----------')
#Extracting a circuit
qasm = extract_circuit(g.copy()).to_basic_gates().to_qasm()
qc2 = QuantumCircuit().from_qasm_str(qasm)
t2 = quantum_info.Operator(qc2).data
print(extract_circuit(g.copy()))  # print the circuit architecture
print(qasm)
print(qc2)
print(t2)

print(compare_tensors(t1, t2))  # unitary matrices
print(compare_tensors(c, extract_circuit(g.copy()).to_basic_gates()))  # circuits

OUTPUT:

Circuit(2 qubits, 0 bits, 3 gates)
OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
h q[0];
h q[1];
cz q[0],q[1];

     ┌───┐   
q_0: ┤ H ├─■─
     ├───┤ │ 
q_1: ┤ H ├─■─
     └───┘   
[[ 0.5+0.j  0.5+0.j  0.5+0.j  0.5+0.j] 
 [ 0.5+0.j -0.5+0.j  0.5+0.j -0.5+0.j] 
 [ 0.5+0.j  0.5+0.j -0.5+0.j -0.5+0.j] 
 [-0.5+0.j  0.5+0.j  0.5+0.j -0.5+0.j]]
-----------
zx.simplify.is_graph_like(g):False     
zx.simplify.is_graph_like(g):False           # After the two suggested simplifications
-----------
Circuit(2 qubits, 0 bits, 3 gates)     
OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
h q[1];
h q[0];
cz q[0], q[1];

     ┌───┐
q_0: ┤ H ├─■─
     ├───┤ │
q_1: ┤ H ├─■─
     └───┘
[[ 0.5+0.j  0.5+0.j  0.5+0.j  0.5+0.j]
 [ 0.5+0.j -0.5+0.j  0.5+0.j -0.5+0.j]
 [ 0.5+0.j  0.5+0.j -0.5+0.j -0.5+0.j]
 [-0.5+0.j  0.5+0.j  0.5+0.j -0.5+0.j]]
True
True

@jvdwetering
Copy link
Collaborator

I think it is because that function has a stricter definition of what graph-like is. In particular, I think it disallows Hadamard edges directly connected to inputs and outputs, or spiders that are both connected to an input and an output. Both of these are fine for the extraction.

@jvdwetering jvdwetering added the bug Something isn't working label Feb 26, 2024
@jvdwetering
Copy link
Collaborator

Can this be closed?

@rcasjimenez
Copy link
Author

Yes, @jvdwetering. Thank you for your support.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants