"""Utility functions used by QCLight.
These are mostly math and bit manipulation functions.
"""
import re
from typing import Iterator
from qclight.error import BinaryStringError, OutOfRangeError
[docs]def bitstring_to_int(
bitstring: "str", start: "int" = 0, end: "int | None" = None
) -> "int":
"""Convert a bitstring to an integer.
Args:
bitstring: bitstring to convert
start: start index of the bitstring
end: end index of the bitstring
Returns:
integer representation of the bitstring
"""
if re.match(r"^[01]+$", bitstring) is None:
raise BinaryStringError(bitstring)
if end is None:
end = len(bitstring)
return int(bitstring[start:end], 2)
[docs]def range_fixed_bits(n_bits: "int", fixed_bits: "int | set[int]") -> "Iterator[int]":
"""Loops over all the numbers from 0 to 2**n_bits
with the bits in the fixed_bits indexes equal to 1.
All the indexes are considered big endian.
Example:
.. code-block:: python
range_fixed_bits(2, set([1])) # 0b01, 0b11
range_fixed_bits(3, set([0])) # 0b100, 0b101, 0b110, 0b111
range_fixed_bits(4, set([2, 3])) # 0b0011, 0b0111, 0b1011, 0b1111
```python
range_fixed_bits(2, set([1])) # 0b01, 0b11
range_fixed_bits(3, set([0])) # 0b100, 0b101, 0b110, 0b111
range_fixed_bits(4, set([2, 3])) # 0b0011, 0b0111, 0b1011, 0b1111
```
Args:
n_bits: number of bits of the values to loop over
fixed_bits: index or list of indexes of the bits equal to 1, big endian
Yields:
all numbers of length n_bits with the fixed_bits equal to 1 and
Raises:
OutOfRangeError: if the control_bits indexes are not valid
"""
if isinstance(fixed_bits, int):
fixed_bits = set([fixed_bits])
if any(i >= n_bits for i in fixed_bits):
raise OutOfRangeError(max_range=n_bits)
# bit mask with the control bits and switch bit set to 1
mask = sum(1 << (n_bits - i - 1) for i in fixed_bits)
out = mask
yield out
for _ in range(((2**n_bits) >> len(fixed_bits)) - 1):
out = (out + 1) | mask
yield out
[docs]def range_fixed_bits_switch(
n_bits: "int", fixed_bits: "int | set[int]", switch_bit: "int"
) -> "Iterator[tuple[int, int]]":
"""Loops over all the numbers from 0 to 2**n_bits
with the bits in the fixed_bits indexes equal to 1.
Each iteration of the loop will return a pair of number.
The first one will have the bit in the switch_bit position equal to 0.
The same bit will be 1 in the second element of the pair.
All the indexes are considered big endian.
Example:
.. code-block:: python
range_fixed_bits_switch(3, set([0]), 1) # (0b100, 0b110), (0b101, 0b111)
range_fixed_bits_switch(4, set([2, 3]), 1) # (0b0011, 0b0111), (0b1011, 0b1111)
range_fixed_bits_switch(5, set([0, 1, 2]), 4) # (0b11100, 0b11101), (0b11110, 0b11111)
```python
range_fixed_bits_switch(3, set([0]), 1) # (0b100, 0b110), (0b101, 0b111)
range_fixed_bits_switch(4, set([2, 3]), 1) # (0b0011, 0b0111), (0b1011, 0b1111)
range_fixed_bits_switch(5, set([0, 1, 2]), 4) # (0b11100, 0b11101), (0b11110, 0b11111)
```
Args:
n_bits: number of bits of the values to loop over
fixed_bits: index or list of indexes of the bits equal to 1, big endian
switch_bit: index of the bit equal to 0/1 in the pairs, big endian
Yields:
all possible pairs of numbers of length n_bits
with the fixed_bits equal to 1 and the switch_bit equal to 0/1
Raises:
OutOfRangeError: if the fixed_bits or switch_bit indexes are not valid
"""
if isinstance(fixed_bits, int):
fixed_bits = set([fixed_bits])
if any(i >= n_bits for i in fixed_bits) or switch_bit >= n_bits:
raise OutOfRangeError(max_range=n_bits)
offset = len(fixed_bits) + 1
# bit mask with the control bits and switch bit set to 1
mask = sum(1 << (n_bits - i - 1) for i in [*fixed_bits, switch_bit])
# bit mask with all bits set to 1 except for the switch bit, set to 0
switch_bit_mask = ~(1 << (n_bits - switch_bit - 1))
out = mask
yield out & switch_bit_mask, out
for _ in range(((2**n_bits) >> offset) - 1):
out = (out + 1) | mask
yield out & switch_bit_mask, out