From 3c7506d144412f02f6389c05dcde31bda95969bb Mon Sep 17 00:00:00 2001 From: knzwnao Date: Mon, 9 Aug 2021 22:57:44 +0900 Subject: [PATCH 01/13] add detach prefix function --- qiskit/utils/__init__.py | 3 +- qiskit/utils/units.py | 45 +++++++++++++++++++- test/python/utils/__init__.py | 13 ++++++ test/python/utils/test_units.py | 74 +++++++++++++++++++++++++++++++++ 4 files changed, 132 insertions(+), 3 deletions(-) create mode 100644 test/python/utils/__init__.py create mode 100644 test/python/utils/test_units.py diff --git a/qiskit/utils/__init__.py b/qiskit/utils/__init__.py index f061b82be161..a0992fa2745c 100644 --- a/qiskit/utils/__init__.py +++ b/qiskit/utils/__init__.py @@ -26,6 +26,7 @@ local_hardware_info is_main_process apply_prefix + detach_prefix Algorithm Utilities =================== @@ -62,7 +63,7 @@ from .deprecation import deprecate_function from .multiprocessing import local_hardware_info from .multiprocessing import is_main_process -from .units import apply_prefix +from .units import apply_prefix, detach_prefix from .circuit_utils import summarize_circuits from .entangler_map import get_entangler_map, validate_entangler_map diff --git a/qiskit/utils/units.py b/qiskit/utils/units.py index ce9414115b00..e1cea952440d 100644 --- a/qiskit/utils/units.py +++ b/qiskit/utils/units.py @@ -12,6 +12,9 @@ """SI unit utilities""" +import numpy as np +from typing import Tuple + def apply_prefix(value: float, unit: str) -> float: """ @@ -29,8 +32,9 @@ def apply_prefix(value: float, unit: str) -> float: Exception: If the units aren't recognized. """ downfactors = {"p": 1e12, "n": 1e9, "u": 1e6, "µ": 1e6, "m": 1e3} - upfactors = {"k": 1e3, "M": 1e6, "G": 1e9} - if not unit: + upfactors = {"k": 1e3, "M": 1e6, "G": 1e9, "T": 1e12} + if not unit or len(unit) == 1: + # "m" can represent meter return value if unit[0] in downfactors: return value / downfactors[unit[0]] @@ -38,3 +42,40 @@ def apply_prefix(value: float, unit: str) -> float: return value * upfactors[unit[0]] else: raise Exception(f"Could not understand units: {unit}") + + +def detach_prefix(value: float) -> Tuple[float, str]: + """ + Given a SI unit value, find the most suitable auxiliary unit to scale the value. + + For example, the ``value = 1.3e8`` will be converted into a tuple of ``(130.0, "M")``, + which represents a scaled value and auxiliary unit that may be used to display the value. + In above example, that value might be displayed as ``130 MHz`` (unit is arbitrary here). + + Example: + + >>> value, prefix = detach_prefix(1e4) + >>> print(f"{value} {prefix}Hz") + 10 kHz + + Args: + value: The number to find prefix. + + Returns: + A tuple of scaled value and prefix. + """ + up_factors = ["k", "M", "G", "T"] + down_factors = ["p", "n", "μ", "m"] + + fixed_point_3n = int(np.floor(np.log10(value) / 3)) + if fixed_point_3n != 0: + if fixed_point_3n > 0: + prefix = up_factors[fixed_point_3n - 1] + else: + prefix = down_factors[fixed_point_3n] + scale = 10 ** (-3 * fixed_point_3n) + else: + prefix = "" + scale = 1.0 + + return scale * value, prefix diff --git a/test/python/utils/__init__.py b/test/python/utils/__init__.py new file mode 100644 index 000000000000..58359d7e2d0b --- /dev/null +++ b/test/python/utils/__init__.py @@ -0,0 +1,13 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Qiskit utilities tests.""" diff --git a/test/python/utils/test_units.py b/test/python/utils/test_units.py new file mode 100644 index 000000000000..5b053bbf1268 --- /dev/null +++ b/test/python/utils/test_units.py @@ -0,0 +1,74 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Test for unit conversion functions.""" + +from qiskit.test import QiskitTestCase +from qiskit.utils import apply_prefix, detach_prefix + + +class TestUnitConversion(QiskitTestCase): + """Test the unit conversion utilities.""" + + def test_apply_prefix(self): + """Test applying prefix to value.""" + ref_values = [ + ([1.0, "THz"], 1e12), + ([1.0, "GHz"], 1e9), + ([1.0, "MHz"], 1e6), + ([1.0, "kHz"], 1e3), + ([1.0, "mHz"], 1e-3), + ([1.0, "µHz"], 1e-6), + ([1.0, "uHz"], 1e-6), + ([1.0, "nHz"], 1e-9), + ([1.0, "pHz"], 1e-12), + ] + + for args, ref_ret in ref_values: + self.assertEqual(apply_prefix(*args), ref_ret) + + def test_not_convert_meter(self): + """Test not apply prefix to meter.""" + self.assertEqual(apply_prefix(1.0, "m"), 1.0) + + def test_detach_prefix(self): + """Test detach prefix from the value.""" + ref_values = [ + (1e12, (1.0, "T")), + (1e11, (100.0, "G")), + (1e10, (10.0, "G")), + (1e9, (1.0, "G")), + (1e8, (100.0, "M")), + (1e7, (10.0, "M")), + (1e6, (1.0, "M")), + (1e5, (100.0, "k")), + (1e4, (10.0, "k")), + (1e3, (1.0, "k")), + (100, (100.0, "")), + (10, (10.0, "")), + (1.0, (1.0, "")), + (0.1, (100.0, "m")), + (0.01, (10.0, "m")), + (1e-3, (1.0, "m")), + (1e-4, (100.0, "μ")), + (1e-5, (10.0, "μ")), + (1e-6, (1.0, "μ")), + (1e-7, (100.0, "n")), + (1e-8, (10.0, "n")), + (1e-9, (1.0, "n")), + (1e-10, (100.0, "p")), + (1e-11, (10.0, "p")), + (1e-12, (1.0, "p")), + ] + + for arg, ref_rets in ref_values: + self.assertTupleEqual(detach_prefix(arg), ref_rets) From 9ee929656f14012350359a2f559a72417f33df3f Mon Sep 17 00:00:00 2001 From: knzwnao Date: Tue, 10 Aug 2021 00:23:55 +0900 Subject: [PATCH 02/13] handling of zero, negative, out of range values --- qiskit/utils/units.py | 32 +++++++++++++++++++++----------- test/python/utils/test_units.py | 18 ++++++++++++++++++ 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/qiskit/utils/units.py b/qiskit/utils/units.py index e1cea952440d..6179660b7489 100644 --- a/qiskit/utils/units.py +++ b/qiskit/utils/units.py @@ -12,9 +12,11 @@ """SI unit utilities""" -import numpy as np +import warnings from typing import Tuple +import numpy as np + def apply_prefix(value: float, unit: str) -> float: """ @@ -64,17 +66,25 @@ def detach_prefix(value: float) -> Tuple[float, str]: Returns: A tuple of scaled value and prefix. """ - up_factors = ["k", "M", "G", "T"] - down_factors = ["p", "n", "μ", "m"] - - fixed_point_3n = int(np.floor(np.log10(value) / 3)) - if fixed_point_3n != 0: - if fixed_point_3n > 0: - prefix = up_factors[fixed_point_3n - 1] + downfactors = ["p", "n", "μ", "m"] + upfactors = ["k", "M", "G", "T"] + + if not value: + return 0.0, "" + + try: + fixed_point_3n = int(np.floor(np.log10(np.abs(value)) / 3)) + if fixed_point_3n != 0: + if fixed_point_3n > 0: + prefix = upfactors[fixed_point_3n - 1] + else: + prefix = downfactors[fixed_point_3n] + scale = 10 ** (-3 * fixed_point_3n) else: - prefix = down_factors[fixed_point_3n] - scale = 10 ** (-3 * fixed_point_3n) - else: + prefix = "" + scale = 1.0 + except IndexError: + warnings.warn(f"The value {value} is out of range. Raw value is returned.", UserWarning) prefix = "" scale = 1.0 diff --git a/test/python/utils/test_units.py b/test/python/utils/test_units.py index 5b053bbf1268..2428bb794924 100644 --- a/test/python/utils/test_units.py +++ b/test/python/utils/test_units.py @@ -72,3 +72,21 @@ def test_detach_prefix(self): for arg, ref_rets in ref_values: self.assertTupleEqual(detach_prefix(arg), ref_rets) + + def test_detach_prefix_with_zero(self): + """Test detach prefix by input zero.""" + self.assertTupleEqual(detach_prefix(0.), (0., "")) + + def test_detach_prefix_with_negative(self): + """Test detach prefix by input negative values.""" + self.assertTupleEqual(detach_prefix(-1.234e7), (-12.34, "M")) + + def test_detach_prefix_with_value_too_large(self): + """Test detach prefix by input too large value.""" + with self.assertWarns(UserWarning): + self.assertTupleEqual(detach_prefix(1e20), (1e20, "")) + + def test_detach_prefix_with_value_too_small(self): + """Test detach prefix by input too small value.""" + with self.assertWarns(UserWarning): + self.assertTupleEqual(detach_prefix(1e-20), (1e-20, "")) From 19f256f8b98b80f11ca563d9d6d7df89641df05b Mon Sep 17 00:00:00 2001 From: knzwnao Date: Tue, 10 Aug 2021 00:25:24 +0900 Subject: [PATCH 03/13] add reno --- .../notes/add-detach-prefix-088e96b88ba29927.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 releasenotes/notes/add-detach-prefix-088e96b88ba29927.yaml diff --git a/releasenotes/notes/add-detach-prefix-088e96b88ba29927.yaml b/releasenotes/notes/add-detach-prefix-088e96b88ba29927.yaml new file mode 100644 index 000000000000..ba8b3ac21c34 --- /dev/null +++ b/releasenotes/notes/add-detach-prefix-088e96b88ba29927.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Add utility function :py:func:`~qiskit.utils.units.detach_prefix` that is a counterpart of + :py:func:`~qiskit.utils.units.apply_prefix`. + The new function returns a tuple of scaled value and prefix from a given float value. + For example, a value ``1.3e8`` will be converted into ``(130, "M")`` that can be + used to display a value in the user friendly format, such as `130 MHz`. From b5383afe25c229c919a5bb9bb15fbda3181b3c6d Mon Sep 17 00:00:00 2001 From: knzwnao Date: Tue, 10 Aug 2021 01:07:37 +0900 Subject: [PATCH 04/13] black --- test/python/utils/test_units.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/utils/test_units.py b/test/python/utils/test_units.py index 2428bb794924..a99204ebfb1b 100644 --- a/test/python/utils/test_units.py +++ b/test/python/utils/test_units.py @@ -75,7 +75,7 @@ def test_detach_prefix(self): def test_detach_prefix_with_zero(self): """Test detach prefix by input zero.""" - self.assertTupleEqual(detach_prefix(0.), (0., "")) + self.assertTupleEqual(detach_prefix(0.0), (0.0, "")) def test_detach_prefix_with_negative(self): """Test detach prefix by input negative values.""" From bffd8b81c937fc64207dad3e99e63dce3a8cec8c Mon Sep 17 00:00:00 2001 From: knzwnao Date: Thu, 12 Aug 2021 02:27:33 +0900 Subject: [PATCH 05/13] update implementation and add precision handling --- qiskit/utils/units.py | 105 +++++++++++++++++++++----------- test/python/utils/test_units.py | 21 +++++-- 2 files changed, 84 insertions(+), 42 deletions(-) diff --git a/qiskit/utils/units.py b/qiskit/utils/units.py index 6179660b7489..055a315f17f0 100644 --- a/qiskit/utils/units.py +++ b/qiskit/utils/units.py @@ -12,8 +12,7 @@ """SI unit utilities""" -import warnings -from typing import Tuple +from typing import Tuple, Optional import numpy as np @@ -31,24 +30,35 @@ def apply_prefix(value: float, unit: str) -> float: Converted value. Raises: - Exception: If the units aren't recognized. + Exception: If the ``units`` aren't recognized. """ - downfactors = {"p": 1e12, "n": 1e9, "u": 1e6, "µ": 1e6, "m": 1e3} - upfactors = {"k": 1e3, "M": 1e6, "G": 1e9, "T": 1e12} + prefactors = { + "f": -15, + "p": -12, + "n": -9, + "u": -6, + "µ": -6, + "m": -3, + "k": 3, + "M": 6, + "G": 9, + "T": 12, + "P": 15, + } + if not unit or len(unit) == 1: - # "m" can represent meter + # for example, "m" can represent meter return value - if unit[0] in downfactors: - return value / downfactors[unit[0]] - elif unit[0] in upfactors: - return value * upfactors[unit[0]] - else: - raise Exception(f"Could not understand units: {unit}") + if unit[0] not in prefactors: + raise Exception(f"Could not understand unit: {unit}") + + return 10 ** prefactors[unit[0]] * value -def detach_prefix(value: float) -> Tuple[float, str]: + +def detach_prefix(value: float, decimal: Optional[int] = None) -> Tuple[float, str]: """ - Given a SI unit value, find the most suitable auxiliary unit to scale the value. + Given a SI unit value, find the most suitable prefix to scale the value. For example, the ``value = 1.3e8`` will be converted into a tuple of ``(130.0, "M")``, which represents a scaled value and auxiliary unit that may be used to display the value. @@ -62,30 +72,51 @@ def detach_prefix(value: float) -> Tuple[float, str]: Args: value: The number to find prefix. + decimal: Optional. An arbitrary integer number to represent a precision of the value. + If specified, it tries to round the mantissa and adjust the prefix to rounded value. + For example, 999_999.91 will become 999.9999 k with ``decimal=4``, + while 1.0 M with ``decimal=3`` or less. Returns: A tuple of scaled value and prefix. + + Raises: + Exception: If the ``value`` is out of range. + ValueError: If the ``value`` is not real number. """ - downfactors = ["p", "n", "μ", "m"] - upfactors = ["k", "M", "G", "T"] - - if not value: - return 0.0, "" - - try: - fixed_point_3n = int(np.floor(np.log10(np.abs(value)) / 3)) - if fixed_point_3n != 0: - if fixed_point_3n > 0: - prefix = upfactors[fixed_point_3n - 1] - else: - prefix = downfactors[fixed_point_3n] - scale = 10 ** (-3 * fixed_point_3n) - else: - prefix = "" - scale = 1.0 - except IndexError: - warnings.warn(f"The value {value} is out of range. Raw value is returned.", UserWarning) - prefix = "" - scale = 1.0 - - return scale * value, prefix + prefactors = { + -15: "f", + -12: "p", + -9: "n", + -6: "µ", + -3: "m", + 0: "", + 3: "k", + 6: "M", + 9: "G", + 12: "T", + 15: "P", + } + + if not np.isreal(value): + raise ValueError(f"Input should be real number. Cannot convert {value}.") + + if np.abs(value) != 0: + pow10 = int(np.floor(np.log10(np.abs(value)) / 3) * 3) + else: + pow10 = 0 + + mant = 10 ** (-pow10) * value + + if decimal is not None: + # Corner case handling + # For example, 999_999.99 can be rounded to 1000.0 k rather than 1.0 M. + mant = np.round(mant, decimal) + if mant >= 1000: + mant /= 1000 + pow10 += 3 + + if pow10 not in prefactors: + raise Exception(f"Value is out of range: {value}") + + return mant, prefactors[pow10] diff --git a/test/python/utils/test_units.py b/test/python/utils/test_units.py index a99204ebfb1b..3378a57c4d3c 100644 --- a/test/python/utils/test_units.py +++ b/test/python/utils/test_units.py @@ -59,9 +59,9 @@ def test_detach_prefix(self): (0.1, (100.0, "m")), (0.01, (10.0, "m")), (1e-3, (1.0, "m")), - (1e-4, (100.0, "μ")), - (1e-5, (10.0, "μ")), - (1e-6, (1.0, "μ")), + (1e-4, (100.0, "µ")), + (1e-5, (10.0, "µ")), + (1e-6, (1.0, "µ")), (1e-7, (100.0, "n")), (1e-8, (10.0, "n")), (1e-9, (1.0, "n")), @@ -83,10 +83,21 @@ def test_detach_prefix_with_negative(self): def test_detach_prefix_with_value_too_large(self): """Test detach prefix by input too large value.""" - with self.assertWarns(UserWarning): + with self.assertRaises(Exception): self.assertTupleEqual(detach_prefix(1e20), (1e20, "")) def test_detach_prefix_with_value_too_small(self): """Test detach prefix by input too small value.""" - with self.assertWarns(UserWarning): + with self.assertRaises(Exception): self.assertTupleEqual(detach_prefix(1e-20), (1e-20, "")) + + def test_rounding(self): + """Test detach prefix with decimal specification.""" + ret = detach_prefix(999_999.991) + self.assertTupleEqual(ret, (999.999991, "k")) + + ret = detach_prefix(999_999.991, decimal=4) + self.assertTupleEqual(ret, (1.0, "M")) + + ret = detach_prefix(999_999.991, decimal=5) + self.assertTupleEqual(ret, (999.99999, "k")) From 929fd4901df1e1ec804895f11e12a9237d0f38f8 Mon Sep 17 00:00:00 2001 From: knzwnao Date: Thu, 12 Aug 2021 14:13:35 +0900 Subject: [PATCH 06/13] fix round off error --- qiskit/utils/units.py | 29 +++++++++++++++++++++++++++-- test/python/utils/test_units.py | 24 ++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/qiskit/utils/units.py b/qiskit/utils/units.py index 055a315f17f0..55e5645274cf 100644 --- a/qiskit/utils/units.py +++ b/qiskit/utils/units.py @@ -12,6 +12,7 @@ """SI unit utilities""" +from decimal import Decimal from typing import Tuple, Optional import numpy as np @@ -29,8 +30,13 @@ def apply_prefix(value: float, unit: str) -> float: Returns: Converted value. + Note: + This may induce tiny value error due to internal representation of float object. + See https://docs.python.org/3/tutorial/floatingpoint.html for details. + Raises: Exception: If the ``units`` aren't recognized. + TypeError: If the ``value`` is not a number. """ prefactors = { "f": -15, @@ -46,6 +52,11 @@ def apply_prefix(value: float, unit: str) -> float: "P": 15, } + try: + value = float(value) + except TypeError as ex: + raise TypeError(f"Input value {value} is not a number.") from ex + if not unit or len(unit) == 1: # for example, "m" can represent meter return value @@ -53,7 +64,10 @@ def apply_prefix(value: float, unit: str) -> float: if unit[0] not in prefactors: raise Exception(f"Could not understand unit: {unit}") - return 10 ** prefactors[unit[0]] * value + # to avoid round-off error of prefactor + _value = Decimal.from_float(value).scaleb(prefactors[unit[0]]) + + return float(_value) def detach_prefix(value: float, decimal: Optional[int] = None) -> Tuple[float, str]: @@ -80,9 +94,14 @@ def detach_prefix(value: float, decimal: Optional[int] = None) -> Tuple[float, s Returns: A tuple of scaled value and prefix. + Note: + This may induce tiny value error due to internal representation of float object. + See https://docs.python.org/3/tutorial/floatingpoint.html for details. + Raises: Exception: If the ``value`` is out of range. ValueError: If the ``value`` is not real number. + TypeError: If the ``value`` is not a number. """ prefactors = { -15: "f", @@ -98,6 +117,11 @@ def detach_prefix(value: float, decimal: Optional[int] = None) -> Tuple[float, s 15: "P", } + try: + value = float(value) + except TypeError as ex: + raise TypeError(f"Input value {value} is not a number.") from ex + if not np.isreal(value): raise ValueError(f"Input should be real number. Cannot convert {value}.") @@ -106,7 +130,8 @@ def detach_prefix(value: float, decimal: Optional[int] = None) -> Tuple[float, s else: pow10 = 0 - mant = 10 ** (-pow10) * value + # to avoid round-off error of prefactor + mant = float(Decimal.from_float(value).scaleb(-pow10)) if decimal is not None: # Corner case handling diff --git a/test/python/utils/test_units.py b/test/python/utils/test_units.py index 3378a57c4d3c..a1085e8f29d9 100644 --- a/test/python/utils/test_units.py +++ b/test/python/utils/test_units.py @@ -12,10 +12,13 @@ """Test for unit conversion functions.""" +from ddt import ddt, data + from qiskit.test import QiskitTestCase from qiskit.utils import apply_prefix, detach_prefix +@ddt class TestUnitConversion(QiskitTestCase): """Test the unit conversion utilities.""" @@ -101,3 +104,24 @@ def test_rounding(self): ret = detach_prefix(999_999.991, decimal=5) self.assertTupleEqual(ret, (999.99999, "k")) + + @data( + -20.791378538739863, + 9.242757760406565, + 2.7366806276451543, + 9.183776167253349, + 7.658091886606501, + -12.21553566621071, + 8.914055281578145, + 1.2518807770035825, + -6.652899195646036, + -4.647159596697976, + ) + def test_get_same_value_after_attach_detach(self, value: float): + """Test if same value can be obtained.""" + unit = "Hz" + + for prefix in ["P", "T", "G", "k", "m", "u", "n", "p", "f"]: + scaled_val = apply_prefix(value, prefix + unit) + test_val, _ = detach_prefix(scaled_val) + self.assertAlmostEqual(test_val, value) From e14f6d8dedceb64abeab4e055fdade57a35b6dab Mon Sep 17 00:00:00 2001 From: knzwnao Date: Thu, 12 Aug 2021 15:48:50 +0900 Subject: [PATCH 07/13] parameter expression handling --- qiskit/utils/units.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/qiskit/utils/units.py b/qiskit/utils/units.py index 55e5645274cf..07805d209f3c 100644 --- a/qiskit/utils/units.py +++ b/qiskit/utils/units.py @@ -13,12 +13,14 @@ """SI unit utilities""" from decimal import Decimal -from typing import Tuple, Optional +from typing import Tuple, Optional, Union + +from qiskit.circuit.parameterexpression import ParameterExpression import numpy as np -def apply_prefix(value: float, unit: str) -> float: +def apply_prefix(value: Union[float, ParameterExpression], unit: str) -> float: """ Given a SI unit prefix and value, apply the prefix to convert to standard SI unit. @@ -52,11 +54,6 @@ def apply_prefix(value: float, unit: str) -> float: "P": 15, } - try: - value = float(value) - except TypeError as ex: - raise TypeError(f"Input value {value} is not a number.") from ex - if not unit or len(unit) == 1: # for example, "m" can represent meter return value @@ -64,6 +61,13 @@ def apply_prefix(value: float, unit: str) -> float: if unit[0] not in prefactors: raise Exception(f"Could not understand unit: {unit}") + try: + value = float(value) + except TypeError as ex: + if isinstance(value, ParameterExpression) and value.parameters: + return pow(10, prefactors[unit[0]]) * value + raise TypeError(f"Input value {value} is not a number.") from ex + # to avoid round-off error of prefactor _value = Decimal.from_float(value).scaleb(prefactors[unit[0]]) From e940571cf050a80eef9aa59c2670346766596b98 Mon Sep 17 00:00:00 2001 From: knzwnao Date: Thu, 12 Aug 2021 16:03:57 +0900 Subject: [PATCH 08/13] fix import order --- qiskit/utils/units.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/utils/units.py b/qiskit/utils/units.py index 07805d209f3c..4fd592689c9e 100644 --- a/qiskit/utils/units.py +++ b/qiskit/utils/units.py @@ -15,10 +15,10 @@ from decimal import Decimal from typing import Tuple, Optional, Union -from qiskit.circuit.parameterexpression import ParameterExpression - import numpy as np +from qiskit.circuit.parameterexpression import ParameterExpression + def apply_prefix(value: Union[float, ParameterExpression], unit: str) -> float: """ From 1ba3e95770076f2afc7c3c88a4cf43cf8c740e16 Mon Sep 17 00:00:00 2001 From: knzwnao Date: Fri, 13 Aug 2021 00:49:51 +0900 Subject: [PATCH 09/13] multiply-or-divide trick --- qiskit/utils/units.py | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/qiskit/utils/units.py b/qiskit/utils/units.py index 4fd592689c9e..6906b78327f9 100644 --- a/qiskit/utils/units.py +++ b/qiskit/utils/units.py @@ -12,7 +12,6 @@ """SI unit utilities""" -from decimal import Decimal from typing import Tuple, Optional, Union import numpy as np @@ -37,8 +36,7 @@ def apply_prefix(value: Union[float, ParameterExpression], unit: str) -> float: See https://docs.python.org/3/tutorial/floatingpoint.html for details. Raises: - Exception: If the ``units`` aren't recognized. - TypeError: If the ``value`` is not a number. + ValueError: If the ``units`` aren't recognized. """ prefactors = { "f": -15, @@ -59,19 +57,15 @@ def apply_prefix(value: Union[float, ParameterExpression], unit: str) -> float: return value if unit[0] not in prefactors: - raise Exception(f"Could not understand unit: {unit}") + raise ValueError(f"Could not understand unit: {unit}") - try: - value = float(value) - except TypeError as ex: - if isinstance(value, ParameterExpression) and value.parameters: - return pow(10, prefactors[unit[0]]) * value - raise TypeError(f"Input value {value} is not a number.") from ex + pow10 = prefactors[unit[0]] # to avoid round-off error of prefactor - _value = Decimal.from_float(value).scaleb(prefactors[unit[0]]) + if pow10 < 0: + return value / 10 ** np.abs(pow10) - return float(_value) + return value * 10 ** pow10 def detach_prefix(value: float, decimal: Optional[int] = None) -> Tuple[float, str]: @@ -103,9 +97,8 @@ def detach_prefix(value: float, decimal: Optional[int] = None) -> Tuple[float, s See https://docs.python.org/3/tutorial/floatingpoint.html for details. Raises: - Exception: If the ``value`` is out of range. + ValueError: If the ``value`` is out of range. ValueError: If the ``value`` is not real number. - TypeError: If the ``value`` is not a number. """ prefactors = { -15: "f", @@ -121,11 +114,6 @@ def detach_prefix(value: float, decimal: Optional[int] = None) -> Tuple[float, s 15: "P", } - try: - value = float(value) - except TypeError as ex: - raise TypeError(f"Input value {value} is not a number.") from ex - if not np.isreal(value): raise ValueError(f"Input should be real number. Cannot convert {value}.") @@ -135,7 +123,10 @@ def detach_prefix(value: float, decimal: Optional[int] = None) -> Tuple[float, s pow10 = 0 # to avoid round-off error of prefactor - mant = float(Decimal.from_float(value).scaleb(-pow10)) + if pow10 > 0: + mant = value / 10 ** pow10 + else: + mant = value * 10 ** np.abs(pow10) if decimal is not None: # Corner case handling From 060153b607f7411f11f0c531820e806ca9373adb Mon Sep 17 00:00:00 2001 From: knzwnao Date: Tue, 17 Aug 2021 21:28:00 +0900 Subject: [PATCH 10/13] update logic --- qiskit/utils/units.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/qiskit/utils/units.py b/qiskit/utils/units.py index 6906b78327f9..22a81d5f8b1b 100644 --- a/qiskit/utils/units.py +++ b/qiskit/utils/units.py @@ -63,9 +63,9 @@ def apply_prefix(value: Union[float, ParameterExpression], unit: str) -> float: # to avoid round-off error of prefactor if pow10 < 0: - return value / 10 ** np.abs(pow10) + return value / pow(10, -pow10) - return value * 10 ** pow10 + return value * pow(10, pow10) def detach_prefix(value: float, decimal: Optional[int] = None) -> Tuple[float, str]: @@ -124,9 +124,9 @@ def detach_prefix(value: float, decimal: Optional[int] = None) -> Tuple[float, s # to avoid round-off error of prefactor if pow10 > 0: - mant = value / 10 ** pow10 + mant = value / pow(10, pow10) else: - mant = value * 10 ** np.abs(pow10) + mant = value * pow(10, -pow10) if decimal is not None: # Corner case handling @@ -137,6 +137,6 @@ def detach_prefix(value: float, decimal: Optional[int] = None) -> Tuple[float, s pow10 += 3 if pow10 not in prefactors: - raise Exception(f"Value is out of range: {value}") + raise ValueError(f"Value is out of range: {value}") return mant, prefactors[pow10] From 9ca288ecdee4293aa0a894da34fcfff19d5299b8 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 19 Aug 2021 04:50:31 +0900 Subject: [PATCH 11/13] Update qiskit/utils/units.py Co-authored-by: Matthew Treinish --- qiskit/utils/units.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qiskit/utils/units.py b/qiskit/utils/units.py index 22a81d5f8b1b..a09479456f42 100644 --- a/qiskit/utils/units.py +++ b/qiskit/utils/units.py @@ -31,7 +31,8 @@ def apply_prefix(value: Union[float, ParameterExpression], unit: str) -> float: Returns: Converted value. - Note: + .. note:: + This may induce tiny value error due to internal representation of float object. See https://docs.python.org/3/tutorial/floatingpoint.html for details. From 595ffbd13a71dc43a19eeaf4203ff4387d71a4c6 Mon Sep 17 00:00:00 2001 From: knzwnao Date: Thu, 19 Aug 2021 04:55:54 +0900 Subject: [PATCH 12/13] add unit prefix test --- test/python/utils/test_units.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/python/utils/test_units.py b/test/python/utils/test_units.py index a1085e8f29d9..b2d88c0424b9 100644 --- a/test/python/utils/test_units.py +++ b/test/python/utils/test_units.py @@ -121,7 +121,13 @@ def test_get_same_value_after_attach_detach(self, value: float): """Test if same value can be obtained.""" unit = "Hz" - for prefix in ["P", "T", "G", "k", "m", "u", "n", "p", "f"]: + for prefix in ["P", "T", "G", "k", "m", "µ", "n", "p", "f"]: scaled_val = apply_prefix(value, prefix + unit) - test_val, _ = detach_prefix(scaled_val) + test_val, ret_prefix = detach_prefix(scaled_val) self.assertAlmostEqual(test_val, value) + self.assertEqual(prefix, ret_prefix) + + def test_get_symbol_mu(self): + """Test if µ is returned rather than u.""" + _, prefix = detach_prefix(3e-6) + self.assertEqual(prefix, "µ") From 5d893cad96dea202e56a8cd456ef9311e600deaa Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 19 Aug 2021 05:10:16 +0900 Subject: [PATCH 13/13] Update qiskit/utils/units.py Co-authored-by: Matthew Treinish --- qiskit/utils/units.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qiskit/utils/units.py b/qiskit/utils/units.py index a09479456f42..76f300805c6c 100644 --- a/qiskit/utils/units.py +++ b/qiskit/utils/units.py @@ -93,7 +93,8 @@ def detach_prefix(value: float, decimal: Optional[int] = None) -> Tuple[float, s Returns: A tuple of scaled value and prefix. - Note: + .. note:: + This may induce tiny value error due to internal representation of float object. See https://docs.python.org/3/tutorial/floatingpoint.html for details.