diff --git a/src/qldpc/abstract/rings_test.py b/src/qldpc/abstract/rings_test.py index b3f79a235..d5d7c688f 100644 --- a/src/qldpc/abstract/rings_test.py +++ b/src/qldpc/abstract/rings_test.py @@ -219,7 +219,7 @@ def test_regular_rep(ring: abstract.GroupRing, pytestconfig: pytest.Config) -> N def test_ring_row_reduction( - ring_alternating4_gf5: abstract.GroupRing, pytestconfig: pytest.Config + ring_dihedral3_gf5: abstract.GroupRing, pytestconfig: pytest.Config ) -> None: """RingArrays can be row reduced in various ways.""" np.random.seed(pytestconfig.getoption("randomly_seed")) @@ -246,25 +246,19 @@ def test_ring_row_reduction( assert np.array_equal(matrix.howell_normal_form(poly=True), matrix_hnf) # matrix components of non-commutative rings get "standardized" to place pivots on the diagonal - ring = ring_alternating4_gf5 + ring = ring_dihedral3_gf5 transformer = ring.get_transformer() - component_transformer = transformer.transformers[-1] - e3_13 = component_transformer.embed( - component_transformer.extended_field([[0, 0, 1], [0, 0, 0], [0, 0, 0]]) - ) - e3_31 = component_transformer.embed( - component_transformer.extended_field([[0, 0, 0], [0, 0, 0], [1, 0, 0]]) - ) - e3_33 = component_transformer.embed( - component_transformer.extended_field([[0, 0, 0], [0, 0, 0], [0, 0, 1]]) - ) + component_transformer = next(ct for ct in transformer.transformers if ct.size == 2) + e2_12 = component_transformer.embed(component_transformer.extended_field([[0, 1], [0, 0]])) + e2_21 = component_transformer.embed(component_transformer.extended_field([[0, 0], [1, 0]])) + e2_22 = component_transformer.embed(component_transformer.extended_field([[0, 0], [0, 1]])) assert np.array_equal( - abstract.RingArray.build([[e3_13]]).howell_normal_form_semisimple(), - abstract.RingArray.build([[e3_33]]), + abstract.RingArray.build([[e2_12]]).howell_normal_form_semisimple(), + abstract.RingArray.build([[e2_22]]), ) assert np.array_equal( - abstract.RingArray.build([[e3_31]]).howell_normal_form_semisimple(right=True), - abstract.RingArray.build([[e3_33]]), + abstract.RingArray.build([[e2_21]]).howell_normal_form_semisimple(right=True), + abstract.RingArray.build([[e2_22]]), ) # RingArray.row_reduce requires semisimple rings diff --git a/src/qldpc/abstract/wedderburn_artin.py b/src/qldpc/abstract/wedderburn_artin.py index fdf9df775..e10f150ad 100644 --- a/src/qldpc/abstract/wedderburn_artin.py +++ b/src/qldpc/abstract/wedderburn_artin.py @@ -23,6 +23,7 @@ import math import operator from collections.abc import Sequence +from typing import Literal import galois import numpy as np @@ -192,10 +193,24 @@ class WedderburnArtinComponentTransformer: decomposition_coefficient_extractor: galois.FieldArray decomposition_coefficient_recombiner: galois.FieldArray - def __init__(self, pci: RingMember, *, seed: np.random.Generator | None = None) -> None: + def __init__( + self, + pci: RingMember, + *, + seed: np.random.Generator | None = None, + galois_compile: Literal["auto", "jit-lookup", "jit-calculate", "python-calculate"] + | None = "python-calculate", + ) -> None: """Initialize from a primitive central idempotent (PCI) of a ring. WARNING: This class assumes--and does not verify--that the provided RingMember is a PCI. + + Args: + pci: A primitive central idempotent of a ring. + seed: Random number generator seed. + galois_compile: The ufunc calculation mode for the galois field extension GF(q^d). + Default: 'python-calculate', which is empirically faster for small field arrays. + See help(galois.GF), and the 'compile' argument in particular. """ if not pci.ring.is_semisimple: raise ValueError("The Wedderburn-Artin decomposition only exists for semisimple rings") @@ -215,7 +230,7 @@ def __init__(self, pci: RingMember, *, seed: np.random.Generator | None = None) self.power_basis = self._get_power_basis(seed) self.power_basis_dual = qldpc.math.get_dual_basis(self.power_basis, validate=False) - self.extended_field = galois.GF(self.field.order**self.degree) + self.extended_field = galois.GF(self.field.order**self.degree, compile=galois_compile) self.embedded_scalars, self.embedded_power_basis = self._get_center_embeddings() self.embedded_power_basis_dual = self._get_embedded_power_basis_dual() diff --git a/src/qldpc/abstract/wedderburn_artin_test.py b/src/qldpc/abstract/wedderburn_artin_test.py index ab6a7681b..90ab01225 100644 --- a/src/qldpc/abstract/wedderburn_artin_test.py +++ b/src/qldpc/abstract/wedderburn_artin_test.py @@ -30,12 +30,29 @@ def test_wedderburn_artin_transformations( ring: abstract.GroupRing, pytestconfig: pytest.Config ) -> None: - """Decompose semisimple rings into simple components. + """Randomized tests for the Wedderburn-Artin transformation of a group ring. - Runs for GroupRing(CyclicGroup(3), field=4) and GroupRing(AlternatingGroup(4), field=5). + Runs for the default rings to test in this library (defined in an appropriate conftest.py). """ - seed = pytestconfig.getoption("randomly_seed") + _test_wedderburn_artin_transformations(ring, pytestconfig.getoption("randomly_seed")) + +def test_wedderburn_artin_transformations_size_three( + ring_alternating4_gf5: abstract.GroupRing, pytestconfig: pytest.Config +) -> None: + """Randomized tests for the Wedderburn-Artin transformation of a group ring. + + AlternatingGroup(4) is the smallest group with a 3-dimensional irreducible representation, + giving a Wedderburn-Artin component of size=3. This test covers the cross-term construction + in WedderburnArtinComponentTransformer._get_matrix_basis, which only runs when size >= 3. + """ + _test_wedderburn_artin_transformations( + ring_alternating4_gf5, pytestconfig.getoption("randomly_seed") + ) + + +def _test_wedderburn_artin_transformations(ring: abstract.GroupRing, seed: int) -> None: + """Randomized tests for the Wedderburn-Artin transformation of a group ring.""" transformer = ring.get_transformer() # the embedding of ring.field = GF(q) scalars is an isomorphism diff --git a/src/qldpc/conftest.py b/src/qldpc/conftest.py index ba604841b..aecff2057 100644 --- a/src/qldpc/conftest.py +++ b/src/qldpc/conftest.py @@ -24,26 +24,31 @@ def ring_cyclic3_gf4(pytestconfig: pytest.Config) -> abstract.GroupRing: @pytest.fixture(scope="session") -def ring_alternating4_gf5(pytestconfig: pytest.Config) -> abstract.GroupRing: +def ring_dihedral3_gf5(pytestconfig: pytest.Config) -> abstract.GroupRing: """Construct a non-commutative ring with a pre-built Wedderburn-Artin transformer.""" + ring = abstract.GroupRing(abstract.DihedralGroup(3), field=5) + ring.get_transformer(seed=pytestconfig.getoption("randomly_seed")) + return ring + + +@pytest.fixture(scope="session") +def ring_alternating4_gf5(pytestconfig: pytest.Config) -> abstract.GroupRing: + """Construct a non-commutative ring with a size-3 matrix component.""" ring = abstract.GroupRing(abstract.AlternatingGroup(4), field=5) ring.get_transformer(seed=pytestconfig.getoption("randomly_seed")) return ring -@pytest.fixture(name="ring", scope="session", params=["cyclic3_gf4", "alternating4_gf5"]) +@pytest.fixture(name="ring", scope="session", params=["cyclic3_gf4", "dihedral3_gf5"]) def rings_to_test( request: pytest.FixtureRequest, - ring_cyclic3_gf2: abstract.GroupRing, ring_cyclic3_gf4: abstract.GroupRing, - ring_alternating4_gf5: abstract.GroupRing, + ring_dihedral3_gf5: abstract.GroupRing, ) -> abstract.GroupRing: """Retrieve a ring for which we have pre-built a Wedderburn-Artin transformer.""" match request.param: - case "cyclic3_gf2": - return ring_cyclic3_gf2 case "cyclic3_gf4": return ring_cyclic3_gf4 - case "alternating4_gf5": - return ring_alternating4_gf5 + case "dihedral3_gf5": + return ring_dihedral3_gf5 raise ValueError(f"Invalid fixture name: {request.param}") # pragma: no cover