From cb95ee1861c07565dcf8c7decf78a9e4c62d79b5 Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson <oscar.gustafsson@gmail.com> Date: Sun, 9 Apr 2023 17:46:27 +0200 Subject: [PATCH] Add quantize function and enums --- b_asic/quantization.py | 139 +++++++++++++++++++++++++++++++ docs_sphinx/api/index.rst | 1 + docs_sphinx/api/quantization.rst | 7 ++ test/test_quantization.py | 28 +++++++ 4 files changed, 175 insertions(+) create mode 100644 b_asic/quantization.py create mode 100644 docs_sphinx/api/quantization.rst create mode 100644 test/test_quantization.py diff --git a/b_asic/quantization.py b/b_asic/quantization.py new file mode 100644 index 00000000..6fae9d27 --- /dev/null +++ b/b_asic/quantization.py @@ -0,0 +1,139 @@ +"""B-ASIC quantization module.""" + +import math +from enum import Enum + +from b_asic.types import Num + + +class Quantization(Enum): + """Quantization types.""" + + ROUNDING = 1 + "Standard two's complement rounding, i.e, tie rounds towards infinity." + + TRUNCATION = 2 + "Two's complement truncation, i.e., round towards negative infinity." + + MAGNITUDE_TRUNCATION = 3 + "Magnitude truncation, i.e., round towards zero." + + JAMMING = 4 + "Jamming/von Neumann rounding, i.e., set the LSB to one" + + UNBIASED_ROUNDING = 5 + "Unbiased rounding, i.e., tie rounds towards even." + + +class Overflow(Enum): + """Overflow types.""" + + TWOS_COMPLEMENT = 1 + "Two's complement overflow, i.e., remove the more significant bits." + + SATURATION = 2 + """ + Two's complement saturation, i.e., overflow return the most postive/negative + number. + """ + + +def quantize( + value: Num, + fractional_bits: int, + integer_bits: int = 1, + quantization: Quantization = Quantization.TRUNCATION, + overflow: Overflow = Overflow.TWOS_COMPLEMENT, +): + r""" + Quantize *value* assuming two's complement representation. + + Quantization happens before overflow, so, e.g., rounding may lead to an overflow. + + The total number of bits is *fractional_bits* + *integer_bits*. However, there is + no check that this will be a positive number. Note that the sign bit is included in these + bits. If *integer_bits* is not given, then use 1, i.e., the result is between + + .. math:: -1 \leq \text{value} \leq 1-2^{-\text{fractional_bits}} + + If *value* is a complex number, the real and imaginary parts are quantized separately. + + Parameters + ---------- + value : int, float, complex + The value to be quantized. + fractional_bits : int + Number of fractional bits, can be negative. + integer_bits : int, default: 1 + Number of integer bits, can be negative. + quantization : :class:`Quantization`, default: :class:`Quantization.TRUNCATION` + Type of quantization. + overflow : :class:`Overflow`, default: :class:`Overflow.TWOS_COMPLEMENT` + Type of overflow. + + Returns + ------- + int, float, complex + The quantized value. + + Examples + -------- + >>> from b_asic.quantization import quantize, Quantization, Overflow + ... + ... quantize(0.3, 4) # Truncate 0.3 using four fractional bits and one integer bit + 0.25 + >>> quantize(0.3, 4, quantization=Quantization.ROUNDING) # As above, but round + 0.3125 + >>> quantize(1.3, 4) # Will overflow + -0.75 + >>> quantize(1.3, 4, 2) # Use two integer bits + 1.25 + >>> quantize(1.3, 4, overflow=Overflow.SATURATION) # use saturation + 0.9375 + >>> quantize(0.3, 4, -1) # Three bits in total, will overflow + -0.25 + + """ + if isinstance(value, complex): + return complex( + quantize( + value.real, + fractional_bits=fractional_bits, + integer_bits=integer_bits, + quantization=quantization, + overflow=overflow, + ), + quantize( + value.imag, + fractional_bits=fractional_bits, + integer_bits=integer_bits, + quantization=quantization, + overflow=overflow, + ), + ) + b = 2**fractional_bits + v = b * value + if quantization is Quantization.TRUNCATION: + v = math.floor(v) + elif quantization is Quantization.ROUNDING: + v = math.floor(v + 0.5) + elif quantization is Quantization.MAGNITUDE_TRUNCATION: + if v >= 0: + v = math.floor(v) + else: + v = math.ceil(v) + elif quantization is Quantization.JAMMING: + v = math.floor(v) | 1 + else: # Quantization.UNBIASED_ROUNDING + v = round(v) + + v = v / b + i = 2 ** (integer_bits - 1) + if overflow is Overflow.SATURATION: + pos_val = i - 1 / b + neg_val = -i + v = max(neg_val, min(v, pos_val)) + else: # Overflow.TWOS_COMPLEMENT + v = (v + i) % (2 * i) - i + + return v diff --git a/docs_sphinx/api/index.rst b/docs_sphinx/api/index.rst index 35981a06..bb4c23eb 100644 --- a/docs_sphinx/api/index.rst +++ b/docs_sphinx/api/index.rst @@ -10,6 +10,7 @@ API operation.rst port.rst process.rst + quantization.rst resources.rst save_load_structure.rst schedule.rst diff --git a/docs_sphinx/api/quantization.rst b/docs_sphinx/api/quantization.rst new file mode 100644 index 00000000..5a50b8b0 --- /dev/null +++ b/docs_sphinx/api/quantization.rst @@ -0,0 +1,7 @@ +*********************** +``b_asic.quantization`` +*********************** + +.. automodule:: b_asic.quantization + :members: + :undoc-members: diff --git a/test/test_quantization.py b/test/test_quantization.py new file mode 100644 index 00000000..2923787b --- /dev/null +++ b/test/test_quantization.py @@ -0,0 +1,28 @@ +from b_asic.quantization import Overflow, Quantization, quantize + + +def test_quantization(): + a = 0.3 + assert quantize(a, 4) == 0.25 + assert quantize(a, 4, quantization=Quantization.TRUNCATION) == 0.25 + assert quantize(a, 4, quantization=Quantization.ROUNDING) == 0.3125 + assert quantize(a, 4, quantization=Quantization.MAGNITUDE_TRUNCATION) == 0.25 + assert quantize(a, 4, quantization=Quantization.JAMMING) == 0.3125 + assert quantize(-a, 4, quantization=Quantization.TRUNCATION) == -0.3125 + assert quantize(-a, 4, quantization=Quantization.ROUNDING) == -0.3125 + assert quantize(-a, 4, quantization=Quantization.MAGNITUDE_TRUNCATION) == -0.25 + assert quantize(-a, 4, quantization=Quantization.JAMMING) == -0.3125 + assert quantize(complex(a, -a), 4) == complex(0.25, -0.3125) + assert quantize( + complex(a, -a), 4, quantization=Quantization.MAGNITUDE_TRUNCATION + ) == complex(0.25, -0.25) + + assert quantize(1.3, 4) == -0.75 + assert quantize(1.3, 4, overflow=Overflow.SATURATION) == 0.9375 + assert quantize(0.97, 4, quantization=Quantization.ROUNDING) == -1.0 + assert ( + quantize( + 0.97, 4, quantization=Quantization.ROUNDING, overflow=Overflow.SATURATION + ) + == 0.9375 + ) -- GitLab