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

Increase Accuracy of OpenQASM 2.0 Dump #8250

Merged
merged 9 commits into from
Nov 18, 2022
2 changes: 1 addition & 1 deletion qiskit/circuit/instruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ def qasm(self):
if self.params:
name_param = "{}({})".format(
name_param,
",".join([pi_check(i, ndigits=8, output="qasm") for i in self.params]),
",".join([pi_check(i, output="qasm", eps=1e-12) for i in self.params]),
)

return self._qasmif(name_param)
Expand Down
25 changes: 18 additions & 7 deletions qiskit/circuit/tools/pi_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
POW_LIST = np.pi ** np.arange(2, 5)


def pi_check(inpt, eps=1e-6, output="text", ndigits=5):
def pi_check(inpt, eps=1e-9, output="text", ndigits=None):
"""Computes if a number is close to an integer
fraction or multiple of PI and returns the
corresponding string.
Expand All @@ -35,8 +35,10 @@ def pi_check(inpt, eps=1e-6, output="text", ndigits=5):
eps (float): EPS to check against.
output (str): Options are 'text' (default),
'latex', 'mpl', and 'qasm'.
ndigits (int): Number of digits to print
if returning raw inpt.
ndigits (int or None): Number of digits to print
if returning raw inpt.
If `None` (default), Python's
default float formatting is used.

Returns:
str: string representation of output.
Expand Down Expand Up @@ -66,7 +68,7 @@ def pi_check(inpt, eps=1e-6, output="text", ndigits=5):
return inpt

def normalize(single_inpt):
if abs(single_inpt) < 1e-14:
if abs(single_inpt) < eps:
return "0"

if output == "text":
Expand Down Expand Up @@ -101,7 +103,10 @@ def normalize(single_inpt):
power = np.where(abs(abs(single_inpt) - POW_LIST) < eps)
if power[0].shape[0]:
if output == "qasm":
str_out = "{:.{}g}".format(single_inpt, ndigits)
if ndigits is None:
str_out = "{}".format(single_inpt)
else:
str_out = "{:.{}g}".format(single_inpt, ndigits)
elif output == "latex":
str_out = f"{neg_str}{pi}^{power[0][0] + 2}"
elif output == "mpl":
Expand All @@ -113,7 +118,10 @@ def normalize(single_inpt):
# Third is a check for a number larger than MAX_FRAC * pi, not a
# multiple or power of pi, since no fractions will exceed MAX_FRAC * pi
if abs(single_inpt) >= (MAX_FRAC * np.pi):
str_out = "{:.{}g}".format(single_inpt, ndigits)
if ndigits is None:
str_out = "{}".format(single_inpt)
else:
str_out = "{:.{}g}".format(single_inpt, ndigits)
return str_out

# Fourth check is for fractions for 1*pi in the numer and any
Expand Down Expand Up @@ -160,7 +168,10 @@ def normalize(single_inpt):
return str_out

# Nothing found
str_out = "{:.{}g}".format(single_inpt, ndigits)
if ndigits is None:
str_out = "{}".format(single_inpt)
else:
str_out = "{:.{}g}".format(single_inpt, ndigits)
return str_out

complex_inpt = complex(inpt)
Expand Down
12 changes: 12 additions & 0 deletions releasenotes/notes/change-qasm-float-output-d69d0c2896f8ecbb.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
features:
- |
The OpenQASM 2 exporter (:meth:`.QuantumCircuit.qasm`) will now emit higher
precision floating point numbers for gate parameters per default. This is
accomplished by relying on Python's built-in float formatting in case no
suitable approximation to some power/fraction of `pi` can be found.

In addition, a tighter bound (`1e-12` instead of `1e-6`) is used for checking
whether a given parameter is close to a fraction/power of `pi`.

Fixed `#7166 <https://github.com/Qiskit/qiskit-terra/issues/7166>`__.
30 changes: 30 additions & 0 deletions test/python/circuit/test_circuit_qasm.py
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,36 @@ def test_circuit_qasm_with_duplicate_invalid_identifiers(self):
for idx, instruction in enumerate(base._data):
self.assertEqual(instruction.operation.name, names[idx])

def test_circuit_qasm_with_double_precision_rotation_angle(self):
"""Test that qasm() emits high precision rotation angles per default."""
from qiskit.circuit.tools.pi_check import MAX_FRAC

qc = QuantumCircuit(1)
qc.p(0.123456789, 0)
qc.p(pi * pi, 0)
qc.p(MAX_FRAC * pi + 1, 0)

expected_qasm = """OPENQASM 2.0;
include "qelib1.inc";
qreg q[1];
p(0.123456789) q[0];
p(9.869604401089358) q[0];
p(51.26548245743669) q[0];\n"""
self.assertEqual(qc.qasm(), expected_qasm)

def test_circuit_qasm_with_rotation_angles_close_to_pi(self):
"""Test that qasm() properly rounds values closer than 1e-12 to pi."""

qc = QuantumCircuit(1)
qc.p(pi + 1e-11, 0)
qc.p(pi + 1e-12, 0)
expected_qasm = """OPENQASM 2.0;
include "qelib1.inc";
qreg q[1];
p(3.141592653599793) q[0];
p(pi) q[0];\n"""
self.assertEqual(qc.qasm(), expected_qasm)

def test_circuit_raises_on_single_bit_condition(self):
"""OpenQASM 2 can't represent single-bit conditions, so test that a suitable error is
printed if this is attempted."""
Expand Down
16 changes: 8 additions & 8 deletions test/python/circuit/test_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,21 @@ class TestPiCheck(QiskitTestCase):
(3.141592653589793, "π"),
(6.283185307179586, "2π"),
(2.99, "2.99"),
(2.999999999999999, "3"),
(2.9999999999999999, "3.0"),
(0.99, "0.99"),
(0.999999999999999, "1"),
(0.99999999999999999, "1.0"),
(pi, "π"),
(-pi, "-π"),
(3 * pi, "3π"),
(-3 * pi, "-3π"),
(pi / 35, "π/35"),
(-pi / 35, "-π/35"),
(3 * pi / 35, "0.26928"),
(-3 * pi / 35, "-0.26928"),
(3 * pi / 35, "0.26927937030769655"),
(-3 * pi / 35, "-0.26927937030769655"),
(pi**2, "π**2"),
(-(pi**2), "-π**2"),
(1e9, "1e+09"),
(-1e9, "-1e+09"),
(1e9, "1000000000.0"),
(-1e9, "-1000000000.0"),
(1e-9, "1e-09"),
(-1e-9, "-1e-09"),
(6 * pi / 11, "6π/11"),
Expand All @@ -64,8 +64,8 @@ class TestPiCheck(QiskitTestCase):
(-1 / pi, "-1/π"),
(6 / (5 * pi), "6/5π"),
(-6 / (5 * pi), "-6/5π"),
(-382578.0 + 0.0234567j, "-3.8258e+05+0.023457j"),
(-382578.0 - 0.0234567j, "-3.8258e+05-0.023457j"),
(-382578.0 + 0.0234567j, "-382578.0+0.0234567j"),
(-382578.0 - 0.0234567j, "-382578.0-0.0234567j"),
]
)
def test_default(self, case):
Expand Down