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

Remove bit_tools and replace with QDType features #1041

Merged
merged 10 commits into from
Jun 11, 2024
34 changes: 30 additions & 4 deletions qualtran/_infra/data_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -492,18 +492,44 @@ def _fxp_dtype(self) -> Fxp:
def is_symbolic(self) -> bool:
return is_symbolic(self.bitsize, self.num_frac)

def to_bits(self, x: Union[float, Fxp]) -> List[int]:
"""Yields individual bits corresponding to binary representation of x"""
self._assert_valid_classical_val(x)
def to_bits(
self, x: Union[float, Fxp], require_exact: bool = True, complement: bool = True
) -> List[int]:
"""Yields individual bits corresponding to binary representation of `x`.

Args:
x: The value to encode.
require_exact: Raise `ValueError` if `x` cannot be exactly represented.
complement: Use twos-complement rather than sign-magnitude representation of negative values.

Raises:
ValueError: If `x` is negative but this `QFxp` is not signed.
"""
if require_exact:
self._assert_valid_classical_val(x)
if x < 0 and not self.signed:
raise ValueError(f"unsigned QFxp cannot represent {x}.")
if self.signed and not complement:
sign = int(x < 0)
x = abs(x)
fxp = x if isinstance(x, Fxp) else Fxp(x)
return [int(x) for x in fxp.like(self._fxp_dtype).bin()]
bits = [int(x) for x in fxp.like(self._fxp_dtype).bin()]
if self.signed and not complement:
bits[0] = sign
return bits

def from_bits(self, bits: Sequence[int]) -> Fxp:
"""Combine individual bits to form x"""
bits_bin = "".join(str(x) for x in bits[:])
fxp_bin = "0b" + bits_bin[: -self.num_frac] + "." + bits_bin[-self.num_frac :]
return Fxp(fxp_bin, dtype=self.fxp_dtype_str)

def to_fixed_width_int(self, x: Union[float, Fxp]) -> int:
"""Returns the interpretation of the binary representation of `x` as an integer. Requires `x` to be nonnegative."""
if x < 0:
raise ValueError("x must be >= 0.")
return int(''.join(str(b) for b in self.to_bits(x, require_exact=False)), 2)

def __attrs_post_init__(self):
if isinstance(self.num_qubits, int):
if self.num_qubits == 1 and self.signed:
Expand Down
56 changes: 55 additions & 1 deletion qualtran/_infra/data_types_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import math
import random

import numpy as np
import pytest
import sympy
Expand Down Expand Up @@ -293,11 +296,15 @@ def test_to_and_from_bits():
assert list(qfxp_4_3.to_bits(0.625)) == [0, 1, 0, 1]
assert qfxp_4_3.from_bits(qfxp_4_3.to_bits(+0.625)).get_val() == +0.625
assert qfxp_4_3.from_bits(qfxp_4_3.to_bits(-0.625)).get_val() == -0.625
assert list(QFxp(4, 3, True).to_bits(-(1 - 0.625))) == [1, 1, 0, 1]
assert list(qfxp_4_3.to_bits(-(1 - 0.625))) == [1, 1, 0, 1]
assert qfxp_4_3.from_bits(qfxp_4_3.to_bits(0.375)).get_val() == 0.375
assert qfxp_4_3.from_bits(qfxp_4_3.to_bits(-0.375)).get_val() == -0.375
with pytest.raises(ValueError):
_ = qfxp_4_3.to_bits(0.1)
assert list(qfxp_4_3.to_bits(0.7, require_exact=False)) == [0, 1, 0, 1]
assert list(qfxp_4_3.to_bits(0.7, require_exact=False, complement=False)) == [0, 1, 0, 1]
assert list(qfxp_4_3.to_bits(-0.7, require_exact=False)) == [1, 0, 1, 1]
assert list(qfxp_4_3.to_bits(-0.7, require_exact=False, complement=False)) == [1, 1, 0, 1]

with pytest.raises(ValueError):
_ = qfxp_4_3.to_bits(1.5)
Expand All @@ -313,3 +320,50 @@ def test_to_and_from_bits():

assert list(QFxp(7, 3, True).to_bits(-4.375)) == [1] + [0, 1, 1] + [1, 0, 1]
assert list(QFxp(7, 3, True).to_bits(+4.625)) == [0] + [1, 0, 0] + [1, 0, 1]


def test_iter_bits():
assert QUInt(2).to_bits(0) == [0, 0]
assert QUInt(2).to_bits(1) == [0, 1]
assert QUInt(2).to_bits(2) == [1, 0]
assert QUInt(2).to_bits(3) == [1, 1]


def test_iter_bits_twos():
assert QInt(4).to_bits(0) == [0, 0, 0, 0]
assert QInt(4).to_bits(1) == [0, 0, 0, 1]
assert QInt(4).to_bits(-2) == [1, 1, 1, 0]
assert QInt(4).to_bits(-3) == [1, 1, 0, 1]
with pytest.raises(ValueError):
_ = QInt(2).to_bits(100)


random.seed(1234)


@pytest.mark.parametrize('val', [random.uniform(-1, 1) for _ in range(10)])
@pytest.mark.parametrize('width', [*range(2, 20, 2)])
@pytest.mark.parametrize('signed', [True, False])
def test_fixed_point(val, width, signed):
if (val < 0) and not signed:
with pytest.raises(ValueError):
_ = QFxp(width + int(signed), width, signed=signed).to_bits(
val, require_exact=False, complement=False
)
else:
bits = QFxp(width + int(signed), width, signed=signed).to_bits(
val, require_exact=False, complement=False
)
if signed:
sign, bits = bits[0], bits[1:]
assert sign == (1 if val < 0 else 0)
val = abs(val)
approx_val = math.fsum([b * (1 / 2 ** (1 + i)) for i, b in enumerate(bits)])
assert math.isclose(val, approx_val, abs_tol=1 / 2**width), (
f'{val}:{approx_val}:{width}',
bits,
)
with pytest.raises(ValueError):
_ = QFxp(width, width).to_fixed_width_int(-val)
bits_from_int = QUInt(width).to_bits(QFxp(width, width).to_fixed_width_int(val))
assert bits == bits_from_int
5 changes: 2 additions & 3 deletions qualtran/bloqs/arithmetic/addition.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@
from qualtran.bloqs.mcmt.and_bloq import And
from qualtran.bloqs.mcmt.multi_control_multi_target_pauli import MultiControlX
from qualtran.cirq_interop import decompose_from_cirq_style_method
from qualtran.cirq_interop.bit_tools import iter_bits, iter_bits_twos_complement
from qualtran.cirq_interop.t_complexity_protocol import TComplexity
from qualtran.drawing import directional_text_box, Text

Expand Down Expand Up @@ -468,9 +467,9 @@ def build_composite_bloq(
# Get binary representation of k and split k into separate wires.
k_split = bb.split(k)
if self.signed:
binary_rep = list(iter_bits_twos_complement(self.k, self.bitsize))
binary_rep = QInt(self.bitsize).to_bits(self.k)
else:
binary_rep = list(iter_bits(self.k, self.bitsize))
binary_rep = QUInt(self.bitsize).to_bits(self.k)

# Apply XGates to qubits in k where the bitstring has value 1. Apply CNOTs when the gate is
# controlled.
Expand Down
27 changes: 13 additions & 14 deletions qualtran/bloqs/arithmetic/addition_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import qualtran.testing as qlt_testing
from qualtran import BloqBuilder, CtrlSpec, QInt, QUInt
from qualtran.bloqs.arithmetic.addition import Add, AddK, OutOfPlaceAdder
from qualtran.cirq_interop.bit_tools import iter_bits, iter_bits_twos_complement
from qualtran.cirq_interop.t_complexity_protocol import TComplexity
from qualtran.cirq_interop.testing import (
assert_circuit_inp_out_cirqsim,
Expand All @@ -48,11 +47,11 @@ def test_add_decomposition(a: int, b: int, num_bits: int):
circuit0 = cirq.Circuit(op)
ancillas = sorted(circuit.all_qubits())[-num_anc:]
initial_state = [0] * (2 * num_bits + num_anc)
initial_state[:num_bits] = list(iter_bits(a, num_bits))
initial_state[num_bits : 2 * num_bits] = list(iter_bits(b, num_bits))
initial_state[:num_bits] = QUInt(num_bits).to_bits(a)
initial_state[num_bits : 2 * num_bits] = QUInt(num_bits).to_bits(b)
final_state = [0] * (2 * num_bits + num_anc)
final_state[:num_bits] = list(iter_bits(a, num_bits))
final_state[num_bits : 2 * num_bits] = list(iter_bits(a + b, num_bits))
final_state[:num_bits] = QUInt(num_bits).to_bits(a)
final_state[num_bits : 2 * num_bits] = QUInt(num_bits).to_bits(a + b)
assert_circuit_inp_out_cirqsim(circuit, qubits + ancillas, initial_state, final_state)
assert_circuit_inp_out_cirqsim(
circuit0, qubits, initial_state[:-num_anc], final_state[:-num_anc]
Expand All @@ -77,11 +76,11 @@ def test_add_diff_size_registers(a, b, num_bits_a, num_bits_b):
circuit0 = cirq.Circuit(op)
ancillas = sorted(circuit.all_qubits())[-num_anc:]
initial_state = [0] * (num_bits_a + num_bits_b + num_anc)
initial_state[:num_bits_a] = list(iter_bits(a, num_bits_a))
initial_state[num_bits_a : num_bits_a + num_bits_b] = list(iter_bits(b, num_bits_b))
initial_state[:num_bits_a] = QUInt(num_bits_a).to_bits(a)
initial_state[num_bits_a : num_bits_a + num_bits_b] = QUInt(num_bits_b).to_bits(b)
final_state = [0] * (num_bits_a + num_bits_b + num_anc)
final_state[:num_bits_a] = list(iter_bits(a, num_bits_a))
final_state[num_bits_a : num_bits_a + num_bits_b] = list(iter_bits(a + b, num_bits_b))
final_state[:num_bits_a] = QUInt(num_bits_a).to_bits(a)
final_state[num_bits_a : num_bits_a + num_bits_b] = QUInt(num_bits_b).to_bits(a + b)
assert_circuit_inp_out_cirqsim(circuit, qubits + ancillas, initial_state, final_state)
assert_circuit_inp_out_cirqsim(
circuit0, qubits, initial_state[:-num_anc], final_state[:-num_anc]
Expand Down Expand Up @@ -149,11 +148,11 @@ def test_subtract(a, b, num_bits):
circuit = cirq.Circuit(cirq.decompose_once(gate.on(*qubits), context=context))
ancillas = sorted(circuit.all_qubits())[-num_anc:]
initial_state = [0] * (2 * num_bits + num_anc)
initial_state[:num_bits] = list(iter_bits_twos_complement(a, num_bits))
initial_state[num_bits : 2 * num_bits] = list(iter_bits_twos_complement(-b, num_bits))
initial_state[:num_bits] = QInt(num_bits).to_bits(a)
initial_state[num_bits : 2 * num_bits] = QInt(num_bits).to_bits(-b)
final_state = [0] * (2 * num_bits + num_bits - 1)
final_state[:num_bits] = list(iter_bits_twos_complement(a, num_bits))
final_state[num_bits : 2 * num_bits] = list(iter_bits_twos_complement(a - b, num_bits))
final_state[:num_bits] = QInt(num_bits).to_bits(a)
final_state[num_bits : 2 * num_bits] = QInt(num_bits).to_bits(a - b)
all_qubits = qubits + ancillas
assert_circuit_inp_out_cirqsim(circuit, all_qubits, initial_state, final_state)

Expand Down Expand Up @@ -265,7 +264,7 @@ def test_out_of_place_adder():
qubit_order = op.qubits
circuit = cirq.Circuit(cirq.decompose_once(op), cirq.decompose_once(op_inv))
for x in range(2**6):
bits = [*iter_bits(x, 10)][::-1]
bits = QUInt(10).to_bits(x)[::-1]
assert_circuit_inp_out_cirqsim(circuit, qubit_order, bits, bits)
assert gate.t_complexity().t == 3 * 4
assert (gate**-1).t_complexity().t == 0
Expand Down
5 changes: 3 additions & 2 deletions qualtran/bloqs/arithmetic/comparison.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@
from qualtran.bloqs.basic_gates import CNOT, TGate, XGate
from qualtran.bloqs.mcmt.and_bloq import And, MultiAnd
from qualtran.bloqs.mcmt.multi_control_multi_target_pauli import MultiControlX
from qualtran.cirq_interop.bit_tools import iter_bits
from qualtran.drawing import WireSymbol
from qualtran.drawing.musical_score import Text, TextBox
from qualtran.symbolics import is_symbolic, SymbolicInt
Expand Down Expand Up @@ -141,7 +140,9 @@ def decompose_from_registers(
# Scan from left to right.
# `are_equal` contains whether the numbers are equal so far.
ancilla = context.qubit_manager.qalloc(int(self.bitsize))
for b, q, a in zip(iter_bits(int(self.less_than_val), int(self.bitsize)), qubits, ancilla):
for b, q, a in zip(
QUInt(int(self.bitsize)).to_bits(int(self.less_than_val)), qubits, ancilla
):
if b:
yield cirq.X(q)
adjoint.append(cirq.X(q))
Expand Down
5 changes: 2 additions & 3 deletions qualtran/bloqs/arithmetic/comparison_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import pytest

import qualtran.testing as qlt_testing
from qualtran import BloqBuilder
from qualtran import BloqBuilder, QUInt
from qualtran.bloqs.arithmetic.comparison import (
_eq_k,
_greater_than,
Expand All @@ -35,7 +35,6 @@
LinearDepthGreaterThan,
SingleQubitCompare,
)
from qualtran.cirq_interop.bit_tools import iter_bits
from qualtran.cirq_interop.t_complexity_protocol import t_complexity, TComplexity
from qualtran.cirq_interop.testing import assert_circuit_inp_out_cirqsim

Expand Down Expand Up @@ -102,7 +101,7 @@ def test_less_than_gate():
@pytest.mark.parametrize("bits", [*range(8)])
@pytest.mark.parametrize("val", [3, 5, 7, 8, 9])
def test_decompose_less_than_gate(bits: int, val: int):
qubit_states = list(iter_bits(bits, 3))
qubit_states = QUInt(3).to_bits(bits)
circuit = cirq.Circuit(
cirq.decompose_once(
LessThanConstant(3, val).on_registers(x=cirq.LineQubit.range(3), target=cirq.q(4))
Expand Down
4 changes: 2 additions & 2 deletions qualtran/bloqs/arithmetic/hamming_weight_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
import cirq
import pytest

from qualtran import QUInt
from qualtran.bloqs.arithmetic import HammingWeightCompute
from qualtran.cirq_interop.bit_tools import iter_bits
from qualtran.cirq_interop.testing import (
assert_circuit_inp_out_cirqsim,
assert_decompose_is_consistent_with_t_complexity,
Expand Down Expand Up @@ -44,7 +44,7 @@ def test_hamming_weight_compute(bitsize: int):
circuit_with_inv = circuit + cirq.Circuit(cirq.decompose_once(op**-1)) # type: ignore[operator]
qubit_order = sorted(circuit_with_inv.all_qubits())
for inp in range(2**bitsize):
input_state = [0] * (junk_bitsize + out_bitsize) + list(iter_bits(inp, bitsize))
input_state = [0] * (junk_bitsize + out_bitsize) + QUInt(bitsize).to_bits(inp)
result = sim.simulate(circuit, initial_state=input_state).dirac_notation()
actual_bits = result[1 + junk_bitsize : 1 + junk_bitsize + out_bitsize]
assert actual_bits == f'{inp.bit_count():0{out_bitsize}b}'
Expand Down
13 changes: 6 additions & 7 deletions qualtran/bloqs/chemistry/trotter/grid_ham/inverse_sqrt.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,8 @@
from attrs import frozen
from numpy.typing import NDArray

from qualtran import Bloq, bloq_example, BloqDocSpec, QAny, QInt, Register, Signature
from qualtran import Bloq, bloq_example, BloqDocSpec, QAny, QFxp, QInt, Register, Signature
from qualtran.bloqs.arithmetic import Add, MultiplyTwoReals, ScaleIntByReal, SquareRealNumber
from qualtran.cirq_interop.bit_tools import float_as_fixed_width_int
from qualtran.cirq_interop.t_complexity_protocol import TComplexity

if TYPE_CHECKING:
Expand Down Expand Up @@ -94,22 +93,22 @@ def build_qrom_data_for_poly_fit(
for i, (a, b) in enumerate(zip(poly_coeffs_a, poly_coeffs_b)):
# In practice we should set x = 0 to some large constant, but we will just skip for now.
# x = 1
_, coeff = float_as_fixed_width_int(a, target_bitsize)
coeff = QFxp(target_bitsize, target_bitsize).to_fixed_width_int(a)
data[i, 1] = coeff
# x = 2
_, coeff = float_as_fixed_width_int(b, target_bitsize)
coeff = QFxp(target_bitsize, target_bitsize).to_fixed_width_int(b)
data[i, 2] = coeff
# x = 3
_, coeff = float_as_fixed_width_int(a / 2 ** (1 / 2), target_bitsize)
coeff = QFxp(target_bitsize, target_bitsize).to_fixed_width_int(a / 2 ** (1 / 2))
data[i, 3] = coeff
start = 4
for k in range(2, selection_bitsize):
_, coeff = float_as_fixed_width_int(a / 2 ** (k / 2), target_bitsize)
coeff = QFxp(target_bitsize, target_bitsize).to_fixed_width_int(a / 2 ** (k / 2))
# Number of time to repeat the data.
data_size = max(1, 2 ** (k - 1))
end = start + data_size
data[i, start:end] = coeff
_, coeff = float_as_fixed_width_int(b / 2 ** (k / 2), target_bitsize)
coeff = QFxp(target_bitsize, target_bitsize).to_fixed_width_int(b / 2 ** (k / 2))
start += data_size
end += data_size
data[i, start:end] = coeff
Expand Down
10 changes: 5 additions & 5 deletions qualtran/bloqs/chemistry/trotter/grid_ham/inverse_sqrt_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import numpy as np
import pytest

from qualtran import QFxp, QUInt
from qualtran.bloqs.basic_gates import TGate
from qualtran.bloqs.chemistry.trotter.grid_ham.inverse_sqrt import (
_nr_inv_sqrt,
Expand All @@ -24,7 +25,6 @@
NewtonRaphsonApproxInverseSquareRoot,
PolynmomialEvaluationInverseSquareRoot,
)
from qualtran.cirq_interop.bit_tools import iter_bits, iter_bits_fixed_point


def test_newton_raphson_inverse_sqrt(bloq_autotester):
Expand Down Expand Up @@ -55,7 +55,7 @@ def test_poly_eval_inverse_sqrt_bloq_counts():


def fixed_point_to_float(x: int, width: int) -> float:
bits = iter_bits(int(x), width)
bits = QUInt(width).to_bits(int(x))
approx_val = np.sum([b * (1 / 2 ** (1 + i)) for i, b in enumerate(bits)])
return approx_val

Expand Down Expand Up @@ -106,7 +106,7 @@ def test_multiply_float_int():
float_width = 24
int_width = 8
val = np.random.random()
fp_bits = iter_bits_fixed_point(val, float_width)
fp_bits = QFxp(float_width, float_width).to_bits(val, require_exact=False)
fp_int = int(''.join(str(b) for b in fp_bits), 2)
int_val = np.random.randint(0, 2**int_width - 1)
result = multiply_fixed_point_float_by_int(fp_int, int_val, float_width, int_width)
Expand All @@ -119,9 +119,9 @@ def test_multiply_floats():
float_width = 24
a = np.random.random()
b = np.random.random()
bits = iter_bits_fixed_point(a, float_width)
bits = QFxp(float_width, float_width).to_bits(a, require_exact=False)
fp_a = int(''.join(str(b) for b in bits), 2)
bits = iter_bits_fixed_point(b, float_width)
bits = QFxp(float_width, float_width).to_bits(b, require_exact=False)
fp_b = int(''.join(str(b) for b in bits), 2)
result = multiply_fixed_point_floats(fp_a, fp_b, float_width)
assert abs(result / 2**float_width - a * b) <= (float_width + 1) / 2**float_width
Loading
Loading