Skip to content

Commit

Permalink
64 bit signed arithmetic support (#57)
Browse files Browse the repository at this point in the history
* feat: add support for negative integers to phir

* feat: add hypothesis-enabled unit tests for BinArray2

Also, clamp only for unsigned integer types

* lint: fix typos and exclude cuquantum_old

* feat: replace BinArray with BinArray2

* fix: set BinArray2's value correctly, or it can convert to ndarray

* build: bump patch version to distinguish in testing

* feat: partition data_types into signed and unsigned

also make size optional for signed integer type

* build: update to correct phir version

Co-authored-by: Ciarán Ryan-Anderson <[email protected]>

---------

Co-authored-by: Ciarán Ryan-Anderson <[email protected]>
  • Loading branch information
qartik and qciaran committed May 16, 2024
1 parent 54b3eae commit 5aaa88c
Show file tree
Hide file tree
Showing 13 changed files with 192 additions and 29 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ updatereqs: upgrade-pip ## Autogenerate requirements.txt
$(VENV_BIN)/pip-compile --extra=tests --no-annotate --no-emit-index-url --output-file=requirements.txt --strip-extras pyproject.toml

metadeps: upgrade-pip ## Install extra dependencies used to develop/build this package
$(VENV_BIN)/pip install -U build pip-tools pre-commit wheel pytest
$(VENV_BIN)/pip install -U build pip-tools pre-commit wheel pytest hypothesis

# Installation
# ------------
Expand Down
7 changes: 4 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "quantum-pecos"
version = "0.6.0.dev1"
version = "0.6.0.dev2"
authors = [
{name = "The PECOS Developers"},
]
Expand All @@ -28,7 +28,7 @@ requires-python = ">=3.10"
license = { file = "LICENSE"}
keywords = ["quantum", "QEC", "simulation", "PECOS"]
dependencies = [
"phir~=0.3.0",
"phir>=0.3.3,<0.4",
"numpy>=1.15.0,<2.0",
"scipy>=1.1.0,<2.0",
"networkx>=2.1.0,<3.0",
Expand Down Expand Up @@ -73,7 +73,8 @@ visualization = [
"plotly~=5.9.0",
]
tests = [
"pytest>=5.0.0"
"pytest>=5.0.0",
"hypothesis"
]
all = [
"quantum-pecos[simulators]",
Expand Down
2 changes: 1 addition & 1 deletion python/pecos/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from pecos import circuit_converters, circuits, decoders, engines, error_models, misc, qeccs, simulators, tools
from pecos.circuits.quantum_circuit import QuantumCircuit
from pecos.engines import circuit_runners
from pecos.engines.cvm.binarray import BinArray
from pecos.engines.cvm.binarray2 import BinArray2 as BinArray
from pecos.engines.hybrid_engine_old import HybridEngine

__all__ = [
Expand Down
22 changes: 7 additions & 15 deletions python/pecos/classical_interpreters/phir_classical_interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@
import warnings
from typing import TYPE_CHECKING, Any

import numpy as np
from phir.model import PHIRModel

from pecos.classical_interpreters.classical_interpreter_abc import ClassicalInterpreter
from pecos.reps.pypmir import PyPMIR
from pecos.reps.pypmir import PyPMIR, signed_data_types, unsigned_data_types
from pecos.reps.pypmir import types as pt

if TYPE_CHECKING:
Expand All @@ -34,16 +33,7 @@ def version2tuple(v):
return tuple(map(int, (v.split("."))))


data_type_map = {
"i8": np.int8,
"i16": np.int16,
"i32": np.int32,
"i64": np.int64,
"u8": np.uint8,
"u16": np.uint16,
"u32": np.uint32,
"u64": np.uint64,
}
data_type_map = signed_data_types | unsigned_data_types

data_type_map_rev = {v: k for k, v in data_type_map.items()}

Expand Down Expand Up @@ -276,7 +266,6 @@ def assign_int(self, cvar, val: int):

cid = self.csym2id[cvar]
dtype = self.cid2dtype[cid]
size = self.cvar_meta[cid].size

cval = self.cenv[cid]
val = dtype(val)
Expand All @@ -286,8 +275,11 @@ def assign_int(self, cvar, val: int):
cval &= ~(1 << i)
cval |= (val & 1) << i

# mask off bits give the size of the register
cval &= (1 << size) - 1
if type(cval) not in signed_data_types.values():
# mask off bits given the size of the register
# (only valid for unsigned data types)
size = self.cvar_meta[cid].size
cval &= (1 << size) - 1
self.cenv[cid] = cval

def handle_cops(self, op):
Expand Down
11 changes: 9 additions & 2 deletions python/pecos/engines/cvm/binarray2.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@

import numpy as np

from pecos.reps.pypmir import unsigned_data_types


class BinArray2:
def __init__(self, size, value=0, dtype=np.int32) -> None:
"""As opposed to the original unsigned 32-bit BinArray, this class defaults to signed 64-bit type."""

def __init__(self, size, value=0, dtype=np.int64) -> None:
self.size = size
self.value = None
self.dtype = dtype
Expand All @@ -33,6 +37,8 @@ def __init__(self, size, value=0, dtype=np.int32) -> None:
def set(self, value):
if isinstance(value, self.dtype):
self.value = value
elif isinstance(value, BinArray2):
self.value = value.value
else:
if isinstance(value, str):
value = int(value, 2)
Expand All @@ -41,7 +47,8 @@ def set(self, value):

def new_val(self, value):
b = BinArray2(self.size, value, self.dtype)
b.clamp(self.size)
if self.dtype in unsigned_data_types.values():
b.clamp(self.size)
return b

def num_bits(self):
Expand Down
2 changes: 1 addition & 1 deletion python/pecos/engines/cvm/classical.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from __future__ import annotations

from pecos.engines.cvm.binarray import BinArray
from pecos.engines.cvm.binarray2 import BinArray2 as BinArray


def set_output(state, circuit, output_spec, output):
Expand Down
2 changes: 1 addition & 1 deletion python/pecos/engines/cvm/wasm.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import pickle
from pathlib import Path

from pecos.engines.cvm.binarray import BinArray
from pecos.engines.cvm.binarray2 import BinArray2 as BinArray
from pecos.engines.cvm.sim_func import sim_exec
from pecos.engines.cvm.wasm_vms.pywasm import read_pywasm
from pecos.engines.cvm.wasm_vms.pywasm3 import read_pywasm3
Expand Down
3 changes: 2 additions & 1 deletion python/pecos/engines/hybrid_engine_old.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@

import numpy as np

from pecos.engines.cvm.classical import BinArray, eval_condition, eval_cop, set_output
from pecos.engines.cvm.binarray2 import BinArray2 as BinArray
from pecos.engines.cvm.classical import eval_condition, eval_cop, set_output
from pecos.engines.cvm.wasm import eval_cfunc, get_ccop
from pecos.error_models.fake_error_model import FakeErrorModel
from pecos.errors import NotSupportedGateError
Expand Down
2 changes: 1 addition & 1 deletion python/pecos/reps/pypmir/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.

from pecos.reps.pypmir.pypmir import PyPMIR
from pecos.reps.pypmir.pypmir import PyPMIR, signed_data_types, unsigned_data_types
24 changes: 22 additions & 2 deletions python/pecos/reps/pypmir/pypmir.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
from math import pi
from typing import TYPE_CHECKING, Callable, TypeVar

import numpy as np

from pecos.reps.pypmir import block_types as blk
from pecos.reps.pypmir import data_types as d
from pecos.reps.pypmir import op_types as op
Expand All @@ -25,6 +27,20 @@

TypeOp = TypeVar("TypeOp", bound=op.Op)

signed_data_types = {
"i8": np.int8,
"i16": np.int16,
"i32": np.int32,
"i64": np.int64,
}

unsigned_data_types = {
"u8": np.uint8,
"u16": np.uint16,
"u32": np.uint32,
"u64": np.uint64,
}


class PyPMIR:
"""Pythonic PECOS Middle-level IR. Used to convert PHIR into an object and optimize the data structure for
Expand Down Expand Up @@ -229,10 +245,14 @@ def from_phir(cls, phir: dict, name_resolver=None) -> PyPMIR:
name = o["data"]

if name == "cvar_define":
data_type = o["data_type"]
size = int(data_type[1:])
if "size" in o:
size = o["size"]
data = d.CVarDefine(
data_type=o["data_type"],
data_type=data_type,
variable=o["variable"],
size=o["size"],
size=size,
cvar_id=len(p.cvar_meta),
metadata=o.get("metadata"),
)
Expand Down
5 changes: 4 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
# pip-compile --extra=tests --no-annotate --no-emit-index-url --output-file=requirements.txt --strip-extras pyproject.toml
#
annotated-types==0.6.0
attrs==23.2.0
contourpy==1.2.0
cycler==0.12.1
fonttools==4.50.0
hypothesis==6.100.1
iniconfig==2.0.0
kiwisolver==1.4.5
markdown-it-py==3.0.0
Expand All @@ -16,7 +18,7 @@ mdurl==0.1.2
networkx==2.8.8
numpy==1.26.4
packaging==24.0
phir==0.3.2
phir==0.3.3
pillow==10.2.0
pluggy==1.4.0
pydantic==2.6.4
Expand All @@ -28,4 +30,5 @@ python-dateutil==2.9.0.post0
rich==13.7.1
scipy==1.12.0
six==1.16.0
sortedcontainers==2.4.0
typing-extensions==4.10.0
35 changes: 35 additions & 0 deletions tests/integration/test_phir_64_bit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from pecos.engines.hybrid_engine import HybridEngine


def bin2int(result: list[str]) -> int:
return int(result[0], base=2)


def test_setting_cvar():
phir = {
"format": "PHIR/JSON",
"version": "0.1.0",
"ops": [
{"data": "cvar_define", "data_type": "i32", "variable": "var_i32"},
{"data": "cvar_define", "data_type": "u32", "variable": "var_u32", "size": 32},
{"data": "cvar_define", "data_type": "i64", "variable": "var_i64"},
{"data": "cvar_define", "data_type": "u64", "variable": "var_u64", "size": 64},
{"data": "cvar_define", "data_type": "i32", "variable": "var_i32neg"},
{"data": "cvar_define", "data_type": "i64", "variable": "var_i64neg"},
{"cop": "=", "returns": ["var_i32"], "args": [2**31 - 1]},
{"cop": "=", "returns": ["var_u32"], "args": [2**32 - 1]},
{"cop": "=", "returns": ["var_i64"], "args": [2**63 - 1]},
{"cop": "=", "returns": ["var_u64"], "args": [2**64 - 1]},
{"cop": "=", "returns": ["var_i32neg"], "args": [-(2**31)]},
{"cop": "=", "returns": ["var_i64neg"], "args": [-(2**63)]},
],
}

results = HybridEngine(qsim="stabilizer").run(program=phir, shots=5)

assert bin2int(results["var_i32"]) == 2**31 - 1
assert bin2int(results["var_u32"]) == 2**32 - 1
assert bin2int(results["var_i64"]) == 2**63 - 1
assert bin2int(results["var_u64"]) == 2**64 - 1
assert bin2int(results["var_i32neg"]) == -(2**31)
assert bin2int(results["var_i64neg"]) == -(2**63)
104 changes: 104 additions & 0 deletions tests/unit/test_binarray.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
from typing import Final

import numpy as np
from hypothesis import assume, given
from hypothesis import strategies as st
from pecos.engines.cvm.binarray2 import BinArray2 as BinArray

DEFAULT_SIZE: Final = 63
MIN: Final = -(2**DEFAULT_SIZE)
MAX: Final = 2**DEFAULT_SIZE - 1
int_range = st.integers(min_value=MIN, max_value=MAX)


@given(st.text(alphabet=["0", "1"], min_size=1))
def test_init(x):
ba = BinArray(x)
assert ba == f"0b{x}"


def test_set_bit():
ba = BinArray("0000")
ba[2] = 1
assert ba == 0b0100


def test_get_bit():
ba = BinArray("1010")
assert ba[2] == 0
assert ba[3] == 1


def test_to_int():
ba = BinArray("1010")
assert int(ba) == 10


@given(int_range, int_range)
def test_addition(x, y):
assume(MIN <= x + y <= MAX)
ba1 = BinArray(DEFAULT_SIZE, x)
ba2 = BinArray(DEFAULT_SIZE, y)
result = ba1 + ba2
assert int(result) == x + y


def test_subtraction():
ba1 = BinArray("1101") # 13
ba2 = BinArray("1010") # 10
result = ba1 - ba2
assert int(result) == 3


@given(int_range, int_range)
def test_multiplication(x, y):
assume(MIN <= x * y <= MAX)
ba1 = BinArray(DEFAULT_SIZE, x)
ba2 = BinArray(DEFAULT_SIZE, y)
result = ba1 * ba2
assert int(result) == x * y


def test_comparison():
ba1 = BinArray("1010") # 10
ba2 = BinArray("1010") # 10
ba3 = BinArray("1101") # 13
assert ba1 == ba2
assert ba1 != ba3
assert ba1 != ba3
assert ba1 < ba3
assert ba3 > ba1


def test_bitwise_and():
ba1 = BinArray("1010") # 10
ba2 = BinArray("1101") # 13
result = ba1 & ba2
assert result == 0b1000


def test_bitwise_or():
ba1 = BinArray("1010") # 10
ba2 = BinArray("1101") # 13
result = ba1 | ba2
assert result == 0b1111


def test_bitwise_xor():
ba1 = BinArray("1010") # 10
ba2 = BinArray("1101") # 13
result = ba1 ^ ba2
assert result == 0b0111


def test_unsigned_bitwise_not():
ba = BinArray("1010", dtype=np.uint64) # 10
result = ~ba
assert result == 0b0101


@given(int_range)
def test_signed_bitwise_not(x):
ba = BinArray(DEFAULT_SIZE, x)
result = ~ba
assert int(result) == -x - 1 # (two's complement)

0 comments on commit 5aaa88c

Please sign in to comment.