Skip to content
Snippets Groups Projects

Add quantize function and enums

Merged Oscar Gustafsson requested to merge quantization into master
4 files
+ 175
0
Compare changes
  • Side-by-side
  • Inline
Files
4
+ 139
0
"""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
Loading