softwareQinc/staq

Phases of the final states shifted

DevelopDaily opened this issue · 12 comments

@meamy You may be aware of the qpp issue that has been fixed by @vsoftco.

I think there may be something to do at the staq side.

I attached two files in.qasm and out.qasm.
2files.zip

The latter is the result of running this command:

./staq -S -O3 -o out.qasm in.qasm

The two files do not produce the same final states. Could you please take a look at them?

To process my QASM files, I did expand the qelib1.inc by adding a few controlled gates and added their references to the the staq source code locally:

    gate cs ctrl, a { cu1(pi/2) ctrl, a; } 
    gate csdg ctrl, a { cu1(-pi/2) ctrl, a; } 
    gate ct ctrl, a { cu1(pi/4) ctrl, a; }     
    gate ctdg ctrl, a { cu1(-pi/4) ctrl, a; } 
    gate cr(theta, phi) ctrl, a { cu3(theta,phi-pi/2,pi/2-phi) ctrl, a; } 

@DevelopDaily Just curious, how to you test the results? Run it via qpp? If yes, does it produce the same state with an older qpp version which didn't have softwareQinc/qpp#99 fixed?

Yes, all my tests are done by qpp. The older version has the same problem regarding this test case.

meamy commented

I can't seem to reproduce the qpp output for the out.qasm file using the command

./staq -S -O3 -o out2.qasm in.qasm

Using the standard qelib1.inc with those additional gates added, the output of qasm2 out2.qasm differs from that of qasm2 out.qasm by a phase of -i.

Could you paste your qelib1.inc file here for testing? Thanks!

    gate u3(theta,phi,lambda) q { U(theta,phi,lambda) q; } 
    gate u2(phi,lambda) q { U(pi/2,phi,lambda) q; } 
    gate u1(lambda) q { U(0,0,lambda) q; } 
    gate cx c,t { CX c,t; } 
    gate cu1(lambda) a,b { u1(lambda/2) a; cx a,b; u1(-lambda/2) b; cx a,b; 
    u1(lambda/2) b; } 
    gate cu3(theta,phi,lambda) c,t { u1((lambda+phi)/2) c; 
    u1((lambda-phi)/2) t; cx c,t; u3(-theta/2,0,-(phi+lambda)/2) t; cx c,t; 
    u3(theta/2,phi,0) t; }
    gate id a { U(0,0,0) a; } 
    gate u0(gamma) q { U(0,0,0) q; } 
    gate x a { u3(pi,0,pi) a; } 
    gate y a { u3(pi,pi/2,pi/2) a; } 
    gate z a { u1(pi) a; } 
    gate h a { u2(0,pi) a; } 
    gate s a { u1(pi/2) a; } 
    gate cs ctrl, a { cu1(pi/2) ctrl, a; } 
    gate sdg a { u1(-pi/2) a; } 
    gate csdg ctrl, a { cu1(-pi/2) ctrl, a; } 
    gate t a { u1(pi/4) a; }
    gate ct ctrl, a { cu1(pi/4) ctrl, a; }     
    gate tdg a { u1(-pi/4) a; } 
    gate ctdg ctrl, a { cu1(-pi/4) ctrl, a; } 
    gate r(theta, phi) a { u3(theta,phi-pi/2,pi/2-phi) a; } 
    gate cr(theta, phi) ctrl, a { cu3(theta,phi-pi/2,pi/2-phi) ctrl, a; } 
    gate rx(theta) a { r(theta,0) a; }  
    gate ry(theta) a { r(theta,pi/2) a; } 
    gate rz(phi) a { u1(phi) a; }  
    gate cz a,b { h b; cx a,b; h b; } 
    gate cy a,b { sdg b; cx a,b; s b; } 
    gate ch a,b { s b; h b; t b; cx a,b; tdg b; h b; sdg b; } 
    gate ccx a,b,c { h c; cx b,c; tdg c; cx a,c; t c; cx b,c; tdg c; cx a,c; 
    t b; t c; h c; cx a,b; t a; tdg b; cx a,b; }
    gate crz(lambda) a,b { u1(lambda/2) b; cx a,b; u1(-lambda/2) b; cx a,b; 
    } 
    gate swap a,b { cx a,b; cx b,a; cx a,b; } 
    gate cswap a,b,c { cx c,b; ccx a,b,c; cx c,b; } 
    gate cry(theta) a,b {cx a,b; ry(-theta/2) b; cx a,b; ry(theta/2) b;}

My results:

./qasm2 < in.qasm

>> Final state:
0   0   0   0   1   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0

./qasm2 < out.qasm

>> Final state:
0   0   0   0   0.92388 + 0.382682i  0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0 
meamy commented

@DevelopDaily Thanks for sending that! I can reproduce the results of out.qasm now with the qelib1.inc pasted above, which appears to primarily differ from the official one's definition of cu3:

gate cu3(theta,phi,lambda) c, t {
  u1((lambda-phi)/2) t;
  cx c,t;
  u3(-theta/2,0,-(phi+lambda)/2) t;
  cx c,t;
  u3(theta/2,phi,0) t;
}

That would explain the phase difference in the results I was getting.

It seems like the issue is due to how particular gates are handled in qpp which may differ from the definition using either versions of qelib. To make sure it's not any optimizations I've turned them off and compiled the following:

./staq -i -o out.qasm in.qasm
./staq -i -o out2.qasm in2.qasm

The files in.qasm and in2.qasm only differ in which qelib1.inc is used, and the only thing staq has done is inlined gates.

./qasm2 < out.qasm

>> Final state:
0   0   0   0   0.92388 + 0.382682i  0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
./qasm2 < out2.qasm

>> Final state:
0   0   0   0   -0.382682 + 0.92388i  0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0 

I get the same outputs with -O3 turned on, so my main concern that it was introduced by the optimizations in staq seems to be unfounded.

On the other hand, both results are different from the output of in.qasm when run in qpp, which is identical regardless which version of qelib1.inc is used.

At any rate, I'm relatively convinced this isn't an error in either case, just that staq and qpp are handling certain gates differently. @vsoftco we'll have to unify the handling of gates in both at some point to avoid these issues. I'll take a closer look at the qasm handling in qpp when I have some more time and see if there's a solution amenable to all. Whatever we decide we should document it somewhere, because I think this issue has come up a few times now.

@meamy A very good catch of the cu3 discrepancy! Thanks. Certainly, it is not my intention to come up with my own version. I got that from the qpp preprocessor.hpp

The peculiar thing is that there are two versions of cu3 definition in the same file preprocessor.hpp, one in the variable std_include_qasm and the other in the std_include. The former matches yours, and the latter mine.

meamy commented

Yes I think this is what I was referring to. At some point we had made some changes to qelib to deal with some of the issues that arise with the phase-shifted versions of gates in the official qelib, but we kept both versions for compatibility.

At any rate sorry for the confusion! We'll work to unify the approach to standard gates. I'll leave this open until we can sort that out.

@vsoftco Maybe it makes sense to have a single "softwareQ" version of the qelib that agrees with qelib1.inc on everything it defines, but also defines some different versions of the gates with different names? Like cu3alt or something?

@meamy Certainly, as we seem to bump into almost-duplicate specs that create confusion. I'll take a look, thanks! One of the issues is that even Qiskit is not fully OpenQASM compliant: it defines CU3 as https://github.com/Qiskit/qiskit-terra/blob/21f78dc5ee236d51697e65db916ccd04f4dfc3bc/qiskit/circuit/library/standard_gates/u3.py#L176 (and we stuck with that in qpp), whereas OpenQASM definition is defined above: #34 (comment)

So we should either comply with one or another. Qiskit seem to have a large user base, so I'm tempted to comply with Qiskit (and in fact I documented that in qpp when defining the gates)

@vsoftco Just a friendly reminder. Whatever you do, please keep in mind the arguments and design decisions you made in the qpp issue 70. I think they are golden.

...issue is that a phase becomes important in a controlled-operation.

The gates in class Gates use the standard Nielsen & Chuang conventions though (the same as the one in the openQASM specs).

In my opinion, it is the QISKIT implementation and the Open QASM spec itself that muddy the water.

The page 5 of the spec says "For example, U(pi/2,0,pi) q[0]; applies a Hadamard gate to qubit q[0]." The Hadamard gate's matrix, according to the formula in the spec, is:

-0.707j   -0.707j
-0.707j   +0.707j

Albeit a Hadamard, it does not look like what people are used to. For that reason, I speculate, the QISKIT people made a design decision, perhaps a wrong one, to shift the phase globally. That is the source of our grief now.

Consequently, I cannot run any applications based on the phase estimation algorithm written in QASM, even though I can run them perfectly when they are written in qpp pure C++ QCircuit.

I kind of like what @meamy suggested. A "softwareQ" version/implementation may be a solution? Then, for all other platforms (with various rotations, endianness, spec interpretations, spec extensions or whatever incompatibilities and quirks), qpp could have some "adapters", "configurations", "convenience" methods, utility tools, etc. to satisfy them. That would keep the qpp core architecturally robust and technically correct.

@DevelopDaily Indeed, thanks. We will fix this soon. My only slight concern is with q++, as it's a header-only library and shouldn't depend on config files etc. We'll figure out a way of keeping the design elegant and address this annoying issue.

@DevelopDaily Fixed with the new parser decoupling from both qpp and staq