Skip to content

Commit 7373ade

Browse files
qartikqciaran
andauthored
64 bit signed arithmetic support (#57)
* 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 <70174051+qciaran@users.noreply.github.com> --------- Co-authored-by: Ciarán Ryan-Anderson <70174051+qciaran@users.noreply.github.com>
1 parent 2e5dda6 commit 7373ade

File tree

13 files changed

+192
-29
lines changed

13 files changed

+192
-29
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ updatereqs: upgrade-pip ## Autogenerate requirements.txt
2828
$(VENV_BIN)/pip-compile --extra=tests --no-annotate --no-emit-index-url --output-file=requirements.txt --strip-extras pyproject.toml
2929

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

3333
# Installation
3434
# ------------

pyproject.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ build-backend = "setuptools.build_meta"
1515

1616
[project]
1717
name = "quantum-pecos"
18-
version = "0.6.0.dev1"
18+
version = "0.6.0.dev2"
1919
authors = [
2020
{name = "The PECOS Developers"},
2121
]
@@ -28,7 +28,7 @@ requires-python = ">=3.10"
2828
license = { file = "LICENSE"}
2929
keywords = ["quantum", "QEC", "simulation", "PECOS"]
3030
dependencies = [
31-
"phir~=0.3.0",
31+
"phir>=0.3.3,<0.4",
3232
"numpy>=1.15.0,<2.0",
3333
"scipy>=1.1.0,<2.0",
3434
"networkx>=2.1.0,<3.0",
@@ -73,7 +73,8 @@ visualization = [
7373
"plotly~=5.9.0",
7474
]
7575
tests = [
76-
"pytest>=5.0.0"
76+
"pytest>=5.0.0",
77+
"hypothesis"
7778
]
7879
all = [
7980
"quantum-pecos[simulators]",

python/pecos/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
from pecos import circuit_converters, circuits, decoders, engines, error_models, misc, qeccs, simulators, tools
3030
from pecos.circuits.quantum_circuit import QuantumCircuit
3131
from pecos.engines import circuit_runners
32-
from pecos.engines.cvm.binarray import BinArray
32+
from pecos.engines.cvm.binarray2 import BinArray2 as BinArray
3333
from pecos.engines.hybrid_engine_old import HybridEngine
3434

3535
__all__ = [

python/pecos/classical_interpreters/phir_classical_interpreter.py

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,10 @@
1515
import warnings
1616
from typing import TYPE_CHECKING, Any
1717

18-
import numpy as np
1918
from phir.model import PHIRModel
2019

2120
from pecos.classical_interpreters.classical_interpreter_abc import ClassicalInterpreter
22-
from pecos.reps.pypmir import PyPMIR
21+
from pecos.reps.pypmir import PyPMIR, signed_data_types, unsigned_data_types
2322
from pecos.reps.pypmir import types as pt
2423

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

3635

37-
data_type_map = {
38-
"i8": np.int8,
39-
"i16": np.int16,
40-
"i32": np.int32,
41-
"i64": np.int64,
42-
"u8": np.uint8,
43-
"u16": np.uint16,
44-
"u32": np.uint32,
45-
"u64": np.uint64,
46-
}
36+
data_type_map = signed_data_types | unsigned_data_types
4737

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

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

277267
cid = self.csym2id[cvar]
278268
dtype = self.cid2dtype[cid]
279-
size = self.cvar_meta[cid].size
280269

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

289-
# mask off bits give the size of the register
290-
cval &= (1 << size) - 1
278+
if type(cval) not in signed_data_types.values():
279+
# mask off bits given the size of the register
280+
# (only valid for unsigned data types)
281+
size = self.cvar_meta[cid].size
282+
cval &= (1 << size) - 1
291283
self.cenv[cid] = cval
292284

293285
def handle_cops(self, op):

python/pecos/engines/cvm/binarray2.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,13 @@
1313

1414
import numpy as np
1515

16+
from pecos.reps.pypmir import unsigned_data_types
17+
1618

1719
class BinArray2:
18-
def __init__(self, size, value=0, dtype=np.int32) -> None:
20+
"""As opposed to the original unsigned 32-bit BinArray, this class defaults to signed 64-bit type."""
21+
22+
def __init__(self, size, value=0, dtype=np.int64) -> None:
1923
self.size = size
2024
self.value = None
2125
self.dtype = dtype
@@ -33,6 +37,8 @@ def __init__(self, size, value=0, dtype=np.int32) -> None:
3337
def set(self, value):
3438
if isinstance(value, self.dtype):
3539
self.value = value
40+
elif isinstance(value, BinArray2):
41+
self.value = value.value
3642
else:
3743
if isinstance(value, str):
3844
value = int(value, 2)
@@ -41,7 +47,8 @@ def set(self, value):
4147

4248
def new_val(self, value):
4349
b = BinArray2(self.size, value, self.dtype)
44-
b.clamp(self.size)
50+
if self.dtype in unsigned_data_types.values():
51+
b.clamp(self.size)
4552
return b
4653

4754
def num_bits(self):

python/pecos/engines/cvm/classical.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
from __future__ import annotations
1313

14-
from pecos.engines.cvm.binarray import BinArray
14+
from pecos.engines.cvm.binarray2 import BinArray2 as BinArray
1515

1616

1717
def set_output(state, circuit, output_spec, output):

python/pecos/engines/cvm/wasm.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import pickle
1313
from pathlib import Path
1414

15-
from pecos.engines.cvm.binarray import BinArray
15+
from pecos.engines.cvm.binarray2 import BinArray2 as BinArray
1616
from pecos.engines.cvm.sim_func import sim_exec
1717
from pecos.engines.cvm.wasm_vms.pywasm import read_pywasm
1818
from pecos.engines.cvm.wasm_vms.pywasm3 import read_pywasm3

python/pecos/engines/hybrid_engine_old.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515

1616
import numpy as np
1717

18-
from pecos.engines.cvm.classical import BinArray, eval_condition, eval_cop, set_output
18+
from pecos.engines.cvm.binarray2 import BinArray2 as BinArray
19+
from pecos.engines.cvm.classical import eval_condition, eval_cop, set_output
1920
from pecos.engines.cvm.wasm import eval_cfunc, get_ccop
2021
from pecos.error_models.fake_error_model import FakeErrorModel
2122
from pecos.errors import NotSupportedGateError

python/pecos/reps/pypmir/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@
99
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
1010
# specific language governing permissions and limitations under the License.
1111

12-
from pecos.reps.pypmir.pypmir import PyPMIR
12+
from pecos.reps.pypmir.pypmir import PyPMIR, signed_data_types, unsigned_data_types

python/pecos/reps/pypmir/pypmir.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
from math import pi
1616
from typing import TYPE_CHECKING, Callable, TypeVar
1717

18+
import numpy as np
19+
1820
from pecos.reps.pypmir import block_types as blk
1921
from pecos.reps.pypmir import data_types as d
2022
from pecos.reps.pypmir import op_types as op
@@ -25,6 +27,20 @@
2527

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

30+
signed_data_types = {
31+
"i8": np.int8,
32+
"i16": np.int16,
33+
"i32": np.int32,
34+
"i64": np.int64,
35+
}
36+
37+
unsigned_data_types = {
38+
"u8": np.uint8,
39+
"u16": np.uint16,
40+
"u32": np.uint32,
41+
"u64": np.uint64,
42+
}
43+
2844

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

231247
if name == "cvar_define":
248+
data_type = o["data_type"]
249+
size = int(data_type[1:])
250+
if "size" in o:
251+
size = o["size"]
232252
data = d.CVarDefine(
233-
data_type=o["data_type"],
253+
data_type=data_type,
234254
variable=o["variable"],
235-
size=o["size"],
255+
size=size,
236256
cvar_id=len(p.cvar_meta),
237257
metadata=o.get("metadata"),
238258
)

requirements.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
# pip-compile --extra=tests --no-annotate --no-emit-index-url --output-file=requirements.txt --strip-extras pyproject.toml
66
#
77
annotated-types==0.6.0
8+
attrs==23.2.0
89
contourpy==1.2.0
910
cycler==0.12.1
1011
fonttools==4.50.0
12+
hypothesis==6.100.1
1113
iniconfig==2.0.0
1214
kiwisolver==1.4.5
1315
markdown-it-py==3.0.0
@@ -16,7 +18,7 @@ mdurl==0.1.2
1618
networkx==2.8.8
1719
numpy==1.26.4
1820
packaging==24.0
19-
phir==0.3.2
21+
phir==0.3.3
2022
pillow==10.2.0
2123
pluggy==1.4.0
2224
pydantic==2.6.4
@@ -28,4 +30,5 @@ python-dateutil==2.9.0.post0
2830
rich==13.7.1
2931
scipy==1.12.0
3032
six==1.16.0
33+
sortedcontainers==2.4.0
3134
typing-extensions==4.10.0

tests/integration/test_phir_64_bit.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from pecos.engines.hybrid_engine import HybridEngine
2+
3+
4+
def bin2int(result: list[str]) -> int:
5+
return int(result[0], base=2)
6+
7+
8+
def test_setting_cvar():
9+
phir = {
10+
"format": "PHIR/JSON",
11+
"version": "0.1.0",
12+
"ops": [
13+
{"data": "cvar_define", "data_type": "i32", "variable": "var_i32"},
14+
{"data": "cvar_define", "data_type": "u32", "variable": "var_u32", "size": 32},
15+
{"data": "cvar_define", "data_type": "i64", "variable": "var_i64"},
16+
{"data": "cvar_define", "data_type": "u64", "variable": "var_u64", "size": 64},
17+
{"data": "cvar_define", "data_type": "i32", "variable": "var_i32neg"},
18+
{"data": "cvar_define", "data_type": "i64", "variable": "var_i64neg"},
19+
{"cop": "=", "returns": ["var_i32"], "args": [2**31 - 1]},
20+
{"cop": "=", "returns": ["var_u32"], "args": [2**32 - 1]},
21+
{"cop": "=", "returns": ["var_i64"], "args": [2**63 - 1]},
22+
{"cop": "=", "returns": ["var_u64"], "args": [2**64 - 1]},
23+
{"cop": "=", "returns": ["var_i32neg"], "args": [-(2**31)]},
24+
{"cop": "=", "returns": ["var_i64neg"], "args": [-(2**63)]},
25+
],
26+
}
27+
28+
results = HybridEngine(qsim="stabilizer").run(program=phir, shots=5)
29+
30+
assert bin2int(results["var_i32"]) == 2**31 - 1
31+
assert bin2int(results["var_u32"]) == 2**32 - 1
32+
assert bin2int(results["var_i64"]) == 2**63 - 1
33+
assert bin2int(results["var_u64"]) == 2**64 - 1
34+
assert bin2int(results["var_i32neg"]) == -(2**31)
35+
assert bin2int(results["var_i64neg"]) == -(2**63)

tests/unit/test_binarray.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
from typing import Final
2+
3+
import numpy as np
4+
from hypothesis import assume, given
5+
from hypothesis import strategies as st
6+
from pecos.engines.cvm.binarray2 import BinArray2 as BinArray
7+
8+
DEFAULT_SIZE: Final = 63
9+
MIN: Final = -(2**DEFAULT_SIZE)
10+
MAX: Final = 2**DEFAULT_SIZE - 1
11+
int_range = st.integers(min_value=MIN, max_value=MAX)
12+
13+
14+
@given(st.text(alphabet=["0", "1"], min_size=1))
15+
def test_init(x):
16+
ba = BinArray(x)
17+
assert ba == f"0b{x}"
18+
19+
20+
def test_set_bit():
21+
ba = BinArray("0000")
22+
ba[2] = 1
23+
assert ba == 0b0100
24+
25+
26+
def test_get_bit():
27+
ba = BinArray("1010")
28+
assert ba[2] == 0
29+
assert ba[3] == 1
30+
31+
32+
def test_to_int():
33+
ba = BinArray("1010")
34+
assert int(ba) == 10
35+
36+
37+
@given(int_range, int_range)
38+
def test_addition(x, y):
39+
assume(MIN <= x + y <= MAX)
40+
ba1 = BinArray(DEFAULT_SIZE, x)
41+
ba2 = BinArray(DEFAULT_SIZE, y)
42+
result = ba1 + ba2
43+
assert int(result) == x + y
44+
45+
46+
def test_subtraction():
47+
ba1 = BinArray("1101") # 13
48+
ba2 = BinArray("1010") # 10
49+
result = ba1 - ba2
50+
assert int(result) == 3
51+
52+
53+
@given(int_range, int_range)
54+
def test_multiplication(x, y):
55+
assume(MIN <= x * y <= MAX)
56+
ba1 = BinArray(DEFAULT_SIZE, x)
57+
ba2 = BinArray(DEFAULT_SIZE, y)
58+
result = ba1 * ba2
59+
assert int(result) == x * y
60+
61+
62+
def test_comparison():
63+
ba1 = BinArray("1010") # 10
64+
ba2 = BinArray("1010") # 10
65+
ba3 = BinArray("1101") # 13
66+
assert ba1 == ba2
67+
assert ba1 != ba3
68+
assert ba1 != ba3
69+
assert ba1 < ba3
70+
assert ba3 > ba1
71+
72+
73+
def test_bitwise_and():
74+
ba1 = BinArray("1010") # 10
75+
ba2 = BinArray("1101") # 13
76+
result = ba1 & ba2
77+
assert result == 0b1000
78+
79+
80+
def test_bitwise_or():
81+
ba1 = BinArray("1010") # 10
82+
ba2 = BinArray("1101") # 13
83+
result = ba1 | ba2
84+
assert result == 0b1111
85+
86+
87+
def test_bitwise_xor():
88+
ba1 = BinArray("1010") # 10
89+
ba2 = BinArray("1101") # 13
90+
result = ba1 ^ ba2
91+
assert result == 0b0111
92+
93+
94+
def test_unsigned_bitwise_not():
95+
ba = BinArray("1010", dtype=np.uint64) # 10
96+
result = ~ba
97+
assert result == 0b0101
98+
99+
100+
@given(int_range)
101+
def test_signed_bitwise_not(x):
102+
ba = BinArray(DEFAULT_SIZE, x)
103+
result = ~ba
104+
assert int(result) == -x - 1 # (two's complement)

0 commit comments

Comments
 (0)