Skip to content

Commit c88c7bb

Browse files
authored
[QGFPoly] Add QGFPoly, a new data type to represent polynomials over galois fields (#1620)
* [QGFPoly] Add a new data type to represent polynomials over galois fields * Address nits and create function to convert to/from Poly class to array of coefficients * Fix formatting
1 parent 7a4c037 commit c88c7bb

3 files changed

Lines changed: 137 additions & 1 deletion

File tree

qualtran/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
BQUInt,
6262
QMontgomeryUInt,
6363
QGF,
64+
QGFPoly,
6465
)
6566

6667
# Internal imports: none

qualtran/_infra/data_types.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,6 +1025,108 @@ def __str__(self):
10251025
return f'QGF({self.characteristic}**{self.degree})'
10261026

10271027

1028+
@attrs.frozen
1029+
class QGFPoly(QDType):
1030+
r"""Univariate Polynomials with coefficients in a Galois Field GF($p^m$).
1031+
1032+
This data type represents a degree-$n$ univariate polynomials
1033+
$f(x)=\sum_{i=0}^{n} a_i x^{i}$ where the coefficients $a_{i}$ of the polynomial
1034+
belong to a Galois Field $GF(p^{m})$.
1035+
1036+
The data type uses the [Galois library](https://mhostetter.github.io/galois/latest/) to
1037+
perform arithmetic over polynomials defined over Galois Fields using the
1038+
[galois.Poly](https://mhostetter.github.io/galois/latest/api/galois.Poly/).
1039+
1040+
Attributes:
1041+
degree: The degree $n$ of the univariate polynomial $f(x)$ represented by this type.
1042+
qgf: An instance of `QGF` that represents the galois field $GF(p^m)$ over which the
1043+
univariate polynomial $f(x)$ is defined.
1044+
1045+
References
1046+
[Polynomials over finite fields](https://mhostetter.github.io/galois/latest/api/galois.Poly/)
1047+
1048+
[Polynomial Arithmetic](https://mhostetter.github.io/galois/latest/basic-usage/poly-arithmetic/)
1049+
"""
1050+
1051+
degree: SymbolicInt
1052+
qgf: QGF
1053+
1054+
@cached_property
1055+
def bitsize(self) -> SymbolicInt:
1056+
"""Bitsize of qubit register required to represent a single instance of this data type."""
1057+
return self.qgf.bitsize * (self.degree + 1)
1058+
1059+
@cached_property
1060+
def num_qubits(self) -> SymbolicInt:
1061+
"""Number of qubits required to represent a single instance of this data type."""
1062+
return self.bitsize
1063+
1064+
def get_classical_domain(self) -> Iterable[Any]:
1065+
"""Yields all possible classical (computational basis state) values representable
1066+
by this type."""
1067+
import itertools
1068+
1069+
from galois import Poly
1070+
1071+
for it in itertools.product(self.qgf.gf_type.elements, repeat=(self.degree + 1)):
1072+
yield Poly(self.qgf.gf_type(it), field=self.qgf.gf_type)
1073+
1074+
@cached_property
1075+
def _quint_equivalent(self) -> QUInt:
1076+
return QUInt(self.num_qubits)
1077+
1078+
def to_gf_coefficients(self, f_x: galois.Poly) -> galois.Array:
1079+
"""Returns a big-endian array of coefficients of the polynomial f(x)."""
1080+
f_x_coeffs = self.qgf.gf_type.Zeros(self.degree + 1)
1081+
f_x_coeffs[self.degree - f_x.degree :] = f_x.coeffs
1082+
return f_x_coeffs
1083+
1084+
def from_gf_coefficients(self, f_x: galois.Array) -> galois.Poly:
1085+
"""Expects a big-endian array of coefficients that represent a polynomial f(x)."""
1086+
return galois.Poly(f_x, field=self.qgf.gf_type)
1087+
1088+
def to_bits(self, x) -> List[int]:
1089+
"""Returns individual bits corresponding to binary representation of x"""
1090+
self.assert_valid_classical_val(x)
1091+
assert isinstance(x, galois.Poly)
1092+
return self.qgf.to_bits_array(self.to_gf_coefficients(x)).reshape(-1).tolist()
1093+
1094+
def from_bits(self, bits: Sequence[int]):
1095+
"""Combine individual bits to form x"""
1096+
reshaped_bits = np.array(bits).reshape((int(self.degree) + 1, int(self.qgf.bitsize)))
1097+
return self.from_gf_coefficients(self.qgf.from_bits_array(reshaped_bits))
1098+
1099+
def assert_valid_classical_val(self, val: Any, debug_str: str = 'val'):
1100+
"""Raises an exception if `val` is not a valid classical value for this type.
1101+
1102+
Args:
1103+
val: A classical value that should be in the domain of this QDType.
1104+
debug_str: Optional debugging information to use in exception messages.
1105+
"""
1106+
if not isinstance(val, galois.Poly):
1107+
raise ValueError(f"{debug_str} should be a {galois.Poly}, not {val!r}")
1108+
if val.field is not self.qgf.gf_type:
1109+
raise ValueError(
1110+
f"{debug_str} should be defined over {self.qgf.gf_type}, not {val.field}"
1111+
)
1112+
if val.degree > self.degree:
1113+
raise ValueError(f"{debug_str} should have a degree <= {self.degree}, not {val.degree}")
1114+
1115+
def is_symbolic(self) -> bool:
1116+
"""Returns True if this qdtype is parameterized with symbolic objects."""
1117+
return is_symbolic(self.degree, self.qgf)
1118+
1119+
def iteration_length_or_zero(self) -> SymbolicInt:
1120+
"""Safe version of iteration length.
1121+
1122+
Returns the iteration_length if the type has it or else zero.
1123+
"""
1124+
return self.qgf.order
1125+
1126+
def __str__(self):
1127+
return f'QGFPoly({self.degree}, {self.qgf!s})'
1128+
1129+
10281130
QAnyInt = (QInt, QUInt, BQUInt, QMontgomeryUInt)
10291131
QAnyUInt = (QUInt, BQUInt, QMontgomeryUInt, QGF)
10301132

qualtran/_infra/data_types_test.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import random
1616
from typing import Any, Sequence, Union
1717

18+
import galois
1819
import numpy as np
1920
import pytest
2021
import sympy
@@ -31,6 +32,7 @@
3132
QDType,
3233
QFxp,
3334
QGF,
35+
QGFPoly,
3436
QInt,
3537
QIntOnesComp,
3638
QMontgomeryUInt,
@@ -167,13 +169,33 @@ def test_qgf():
167169
assert is_symbolic(qgf_pm)
168170

169171

172+
def test_qgf_poly():
173+
qgf_poly_4_8 = QGFPoly(4, QGF(characteristic=2, degree=3))
174+
assert str(qgf_poly_4_8) == 'QGFPoly(4, QGF(2**3))'
175+
assert qgf_poly_4_8.num_qubits == 5 * 3
176+
n, p, m = sympy.symbols('n, p, m', integer=True, positive=True)
177+
qgf_poly_n_pm = QGFPoly(n, QGF(characteristic=p, degree=m))
178+
assert qgf_poly_n_pm.num_qubits == (n + 1) * ceil(log2(p**m))
179+
assert is_symbolic(qgf_poly_n_pm)
180+
181+
170182
@pytest.mark.parametrize('qdtype', [QBit(), QInt(4), QUInt(4), BQUInt(3, 5)])
171183
def test_domain_and_validation(qdtype: QDType):
172184
for v in qdtype.get_classical_domain():
173185
qdtype.assert_valid_classical_val(v)
174186

175187

176-
@pytest.mark.parametrize('qdtype', [QBit(), QInt(4), QUInt(4), BQUInt(3, 5), QGF(2, 8)])
188+
@pytest.mark.parametrize(
189+
'qdtype',
190+
[
191+
QBit(),
192+
QInt(4),
193+
QUInt(4),
194+
BQUInt(3, 5),
195+
QGF(2, 8),
196+
QGFPoly(4, QGF(characteristic=2, degree=2)),
197+
],
198+
)
177199
def test_domain_and_validation_arr(qdtype: QDType):
178200
arr = np.array(list(qdtype.get_classical_domain()))
179201
qdtype.assert_valid_classical_val_array(arr)
@@ -207,6 +229,11 @@ def test_validation_errs():
207229
with pytest.raises(ValueError):
208230
QGF(2, 8).assert_valid_classical_val(2**8)
209231

232+
with pytest.raises(ValueError):
233+
qgf = QGF(2, 3)
234+
poly = galois.Poly(qgf.gf_type([1, 2, 3, 4, 5, 6, 7]), field=qgf.gf_type)
235+
QGFPoly(4, qgf).assert_valid_classical_val(poly)
236+
210237

211238
def test_validate_arrays():
212239
rs = np.random.RandomState(52)
@@ -319,6 +346,12 @@ def test_qgf_to_and_from_bits():
319346
assert_to_and_from_bits_array_consistent(qgf_256, gf256([*range(256)]))
320347

321348

349+
def test_qgf_poly_to_and_from_bits():
350+
qgf_4 = QGF(2, 2)
351+
qgf_poly_2_4 = QGFPoly(2, qgf_4)
352+
assert_to_and_from_bits_array_consistent(qgf_poly_2_4, [*qgf_poly_2_4.get_classical_domain()])
353+
354+
322355
def test_quint_to_and_from_bits():
323356
quint4 = QUInt(4)
324357
assert [*quint4.get_classical_domain()] == [*range(0, 16)]

0 commit comments

Comments
 (0)