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
2 changes: 1 addition & 1 deletion checks/all_.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@

if __name__ == "__main__":
skip_files = ["--sysmon", "--skip", "configs", "requirements", "build_docs"]
exit(checks_superstaq.all_.run(*skip_files, *sys.argv[1:]))
sys.exit(checks_superstaq.all_.run(*skip_files, *sys.argv[1:]))
2 changes: 1 addition & 1 deletion checks/build_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
import checks_superstaq as checks

if __name__ == "__main__":
exit(checks.build_docs.run(*sys.argv[1:]))
sys.exit(checks.build_docs.run(*sys.argv[1:]))
2 changes: 1 addition & 1 deletion checks/coverage_.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import pytest_

if __name__ == "__main__":
exit(
sys.exit(
checks_superstaq.coverage_.run(
*sys.argv[1:], "--modular", "--sysmon", exclude=pytest_.EXCLUDE
)
Expand Down
2 changes: 1 addition & 1 deletion checks/format_.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
import checks_superstaq

if __name__ == "__main__":
exit(checks_superstaq.format_.run(*sys.argv[1:]))
sys.exit(checks_superstaq.format_.run(*sys.argv[1:]))
2 changes: 1 addition & 1 deletion checks/lint_.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
import checks_superstaq

if __name__ == "__main__":
exit(checks_superstaq.lint_.run(*sys.argv[1:]))
sys.exit(checks_superstaq.lint_.run(*sys.argv[1:]))
2 changes: 1 addition & 1 deletion checks/mypy_.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@
)

if __name__ == "__main__":
exit(checks_superstaq.mypy_.run(*sys.argv[1:], exclude=EXCLUDE))
sys.exit(checks_superstaq.mypy_.run(*sys.argv[1:], exclude=EXCLUDE))
2 changes: 1 addition & 1 deletion checks/pytest_.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@
)

if __name__ == "__main__":
exit(checks_superstaq.pytest_.run(*sys.argv[1:], exclude=EXCLUDE))
sys.exit(checks_superstaq.pytest_.run(*sys.argv[1:], exclude=EXCLUDE))
5 changes: 2 additions & 3 deletions examples/scripts/find_bbcode_layouts.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -421,8 +421,7 @@ def get_max_comm_distance(
for neighbor in code.graph.successors(node):
loc_1 = get_qubit_pos(neighbor)
squared_distance = (loc_0[0] - loc_1[0]) ** 2 + (loc_0[1] - loc_1[1]) ** 2
if squared_distance > max_squared_distance:
max_squared_distance = squared_distance
max_squared_distance = max(max_squared_distance, squared_distance)

return math.sqrt(max_squared_distance)

Expand Down Expand Up @@ -486,7 +485,7 @@ def get_completed_qubit_pos_func(

# assign check qubits to candidate locations
biadjacency_matrix = placement_matrix <= (max_comm_distance + 1e-15) ** 2
check_loc_indices, check_qubit_indices = scipy.optimize.linear_sum_assignment(
_check_loc_indices, check_qubit_indices = scipy.optimize.linear_sum_assignment(
biadjacency_matrix, maximize=True
)

Expand Down
2 changes: 1 addition & 1 deletion experiments/bivariate_bicycle/run_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def run_and_save(
nn, kk, dd = cache[key]
# if we know the distance or the encoding rate is too low, there's nothing more to do
if dd is not None or kk < nn * min_rate:
return None
return

# compute and save code parameters
params = get_quasi_cyclic_code_params(dims, exponents, min_rate, num_trials, silent=silent)
Expand Down
2 changes: 1 addition & 1 deletion experiments/quantum_tanner/collect_results.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
# collect all data for into a list
data = []
for path in glob.glob(paths):
seed = path.split("_")[-1].strip(".txt")[1:]
seed = path.split("_")[-1].removesuffix(".txt")[1:]
with open(path, "r") as file:
num_trials = ast.literal_eval(file.readline().split(":")[-1])
param_text = file.readline().split(":")[-1]
Expand Down
6 changes: 3 additions & 3 deletions experiments/quantum_tanner/run_randomized_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def get_mittal_code(length: int) -> codes.ClassicalCode:
code = full_code.shortened([3])
else:
raise ValueError(f"Unrecognized length for {name}: {length}")
setattr(code, "_name", name)
code._name = name
return code


Expand Down Expand Up @@ -99,7 +99,7 @@ def run_and_save(
if group_order < base_code.num_bits:
# No subset of this group can be large enough to have as many elements as there are bits in
# the base code, so random quantum Tanner codes with the given input data do not exist.
return None
return

group_id = f"SmallGroup-{group_order}-{group_index}"
seed = get_deterministic_hash(group_order, group_index, base_code.matrix.tobytes(), sample)
Expand All @@ -108,7 +108,7 @@ def run_and_save(

if os.path.isfile(path) and not override_existing_data:
# we already have the data for this code, so there is nothing to do
return None
return

if not silent:
job_id = f"{group_id} {base_code_id} {sample}/{num_samples}"
Expand Down
2 changes: 1 addition & 1 deletion experiments/quantum_tanner/verify_codes.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def get_code(code_name: str, code_param: str) -> codes.ClassicalCode:
parts = path.split("_")
group_order, group_index = map(int, parts[-3].split("-")[-2:])
code_name, code_param = parts[-2].split("-")
seed = int(parts[-1].strip(".txt")[1:])
seed = int(parts[-1].removesuffix(".txt")[1:])

group_id = f"SmallGroup-{group_order}-{group_index}"
base_code_id = parts[-2]
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ build-backend = "uv_build"

[tool.ruff]
line-length = 100
lint.extend-select = ["I", "W505"]
lint.extend-select = ["I", "W505", "ANN204"]
lint.pycodestyle.max-doc-length = 100

[tool.mypy]
Expand Down
40 changes: 40 additions & 0 deletions src/qldpc/_util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""Miscellaneous internal utilities

Copyright 2023 The qLDPC Authors and Infleqtion Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "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 __future__ import annotations

from collections.abc import Callable
from typing import TypeVar

CallableType = TypeVar("CallableType", bound=Callable[..., object])


def format_docstring(**substitutions: object) -> Callable[[CallableType], CallableType]:
"""Substitute named values into a function's docstring via str.format.

This lets a docstring reference values (such as module-level constants) by name, for example
"Default: {error_rate}.", without making the docstring an f-string. An f-string cannot be used
as a docstring: Python evaluates it as an ordinary expression and leaves __doc__ set to None,
silently discarding the documentation.
"""

def decorator(func: CallableType) -> CallableType:
if func.__doc__ is not None:
func.__doc__ = func.__doc__.format(**substitutions)
return func

return decorator
39 changes: 39 additions & 0 deletions src/qldpc/_util_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""Unit tests for _util.py

Copyright 2023 The qLDPC Authors and Infleqtion Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "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 qldpc._util import format_docstring


def test_format_docstring() -> None:
"""Named values are substituted into a docstring."""

@format_docstring(value=1e-3, name="tag")
def func() -> None:
"""A docstring with a {value} and a {name}."""

assert func.__doc__ == "A docstring with a 0.001 and a tag."


def test_format_docstring_without_docstring() -> None:
"""A function without a docstring is left untouched."""

@format_docstring(value=1)
def func() -> None:
return None

assert func.__doc__ is None
assert func() is None
18 changes: 9 additions & 9 deletions src/qldpc/abstract/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,25 +42,25 @@
"AlternatingGroup",
"CyclicGroup",
"DihedralGroup",
"Element",
"Group",
"GroupMember",
"GroupRing",
"ProjectiveSpecialLinearGroup",
"Protograph",
"QuaternionGroup",
"RingArray",
"RingMember",
"SmallGroup",
"SpecialLinearGroup",
"SymmetricGroup",
"TrivialGroup",
"get_coefficient_and_exponents",
"resolve_field",
"WedderburnArtinComponentTransformer",
"WedderburnArtinTransformer",
"block_diag",
"get_coefficient_and_exponents",
"get_howell_dual",
"kron",
"matmul",
"Element",
"GroupRing",
"Protograph",
"RingArray",
"RingMember",
"WedderburnArtinComponentTransformer",
"WedderburnArtinTransformer",
"resolve_field",
]
40 changes: 27 additions & 13 deletions src/qldpc/abstract/groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
import math
import operator
from collections.abc import Callable, Iterator, Sequence
from typing import Any, Union
from typing import Any, ClassVar

import galois
import numpy as np
Expand All @@ -60,7 +60,7 @@ def resolve_field(
return field


NestedSequence = Sequence[Union[object, Sequence["NestedSequence"]]]
NestedSequence = Sequence[object | Sequence["NestedSequence"]]

################################################################################
# groups and group members
Expand Down Expand Up @@ -251,7 +251,7 @@ def index(self, member: GroupMember) -> int:
"""The index of a GroupMember in this group."""
index = self._members.get(member)
if not isinstance(index, int):
raise ValueError(f"Member {member} not in group {self}")
raise TypeError(f"Member {member} not in group {self}")
return index

def __mul__(self, other: Group) -> Group:
Expand Down Expand Up @@ -279,7 +279,6 @@ def random(self, *, seed: int | None = None) -> GroupMember:

return GroupMember.from_sympy(self._group.random())

@functools.cache
def regular_lift(self, member: GroupMember, *, right: bool = False) -> npt.NDArray[np.int_]:
"""Lift a group member to its regular representation.

Expand All @@ -298,13 +297,19 @@ def regular_lift(self, member: GroupMember, *, right: bool = False) -> npt.NDArr
See:
- https://en.wikipedia.org/wiki/Regular_representation
"""
matrix = np.zeros([self.order] * 2, dtype=int)
for ii, hh in enumerate(self.generate()):
jj = self.index(hh * member) if right else self.index(member * hh)
matrix[jj, ii] = 1
return matrix
if (member, right) not in self._regular_lift_cache:
matrix = np.zeros([self.order] * 2, dtype=int)
for ii, hh in enumerate(self.generate()):
jj = self.index(hh * member) if right else self.index(member * hh)
matrix[jj, ii] = 1
self._regular_lift_cache[member, right] = matrix
return self._regular_lift_cache[member, right]

@functools.cached_property
def _regular_lift_cache(self) -> dict[tuple[GroupMember, bool], npt.NDArray[np.int_]]:
"""Per-instance cache for regular_lift (avoids the memory leak of caching on methods)."""
return {}

@functools.cache
def adjoint_lift(self, member: GroupMember) -> npt.NDArray[np.int_]:
r"""Lift a group member to its adjoint representation.

Expand All @@ -315,7 +320,16 @@ def adjoint_lift(self, member: GroupMember) -> npt.NDArray[np.int_]:

If the group is abelian, the adjoint lift of every group member is the identity matrix.
"""
return self.regular_lift(member) @ self.regular_lift(member, right=True).T
if member not in self._adjoint_lift_cache:
self._adjoint_lift_cache[member] = (
self.regular_lift(member) @ self.regular_lift(member, right=True).T
)
return self._adjoint_lift_cache[member]

@functools.cached_property
def _adjoint_lift_cache(self) -> dict[GroupMember, npt.NDArray[np.int_]]:
"""Per-instance cache for adjoint_lift (avoids the memory leak of caching on methods)."""
return {}

@functools.cached_property
def inversion_matrix(self) -> npt.NDArray[np.int_]:
Expand Down Expand Up @@ -639,7 +653,7 @@ class QuaternionGroup(Group):

# multiplication table for this group

_table = [
_table: ClassVar = [
[0, 1, 2, 3, 4, 5, 6, 7],
[1, 4, 3, 6, 5, 0, 7, 2],
[2, 7, 4, 1, 6, 3, 0, 5],
Expand Down Expand Up @@ -873,7 +887,7 @@ def __init__(
for index, vec_bytes in enumerate(target_space):
vec = np.frombuffer(vec_bytes, dtype=np.uint8).view(self.field)
next_orbit = [root * member @ vec for root in roots]
next_vec = next((vec for vec in next_orbit if vec.tobytes() in target_space))
next_vec = next(vec for vec in next_orbit if vec.tobytes() in target_space)
next_index = target_space.index(next_vec.tobytes())
perm[index] = next_index
generators.append(GroupMember(perm))
Expand Down
3 changes: 2 additions & 1 deletion src/qldpc/abstract/groups_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,11 @@ def test_permutation_group(pytestconfig: pytest.Config) -> None:

assert abstract.Group.from_generating_mats([[1]]) == abstract.CyclicGroup(1)

with pytest.raises(ValueError, match="not in group"):
with pytest.raises(TypeError, match="not in group"):
abstract.CyclicGroup(1).index(abstract.GroupMember(2, 1))

assert isinstance(hash(group.hashable_generators()), int)
assert isinstance(hash(group), int)


def test_trivial_group() -> None:
Expand Down
2 changes: 1 addition & 1 deletion src/qldpc/abstract/linalg.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def get_howell_dual(
WARNING: This method assumes--and does not verify--that matrix_hnf is in Howell normal form.
"""
ring = matrix_hnf.ring
transformer = transformer = transformer or ring.get_transformer()
transformer = transformer or ring.get_transformer()
dual_matrix = np.zeros(matrix_hnf.shape, dtype=object)
for row, col in enumerate(math.first_nonzero_cols(matrix_hnf)):
pivot = matrix_hnf[row, col].copy()
Expand Down
Loading
Loading