Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 10 additions & 16 deletions src/qldpc/abstract/rings_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand All @@ -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
Expand Down
19 changes: 17 additions & 2 deletions src/qldpc/abstract/wedderburn_artin.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import math
import operator
from collections.abc import Sequence
from typing import Literal

import galois
import numpy as np
Expand Down Expand Up @@ -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")
Expand All @@ -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()

Expand Down
23 changes: 20 additions & 3 deletions src/qldpc/abstract/wedderburn_artin_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
21 changes: 13 additions & 8 deletions src/qldpc/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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