-
Notifications
You must be signed in to change notification settings - Fork 7
Reimplement flow matching using Gaussian elimination #45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3041b76
7b2fffc
91268c7
99bf2ac
6ddaa3b
cc13aa1
b2502aa
5e34260
49922d4
856a2e6
cd93591
97bc2fb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,15 +4,15 @@ build-backend = "setuptools.build_meta" | |
|
|
||
| [project] | ||
| name = "tqecd" | ||
| version = "0.1.3" | ||
| version = "0.1.4" | ||
| authors = [ | ||
| { name = "TQEC community", email = "tqec-design-automation@googlegroups.com" }, | ||
| ] | ||
| description = "Automatically find detectors in a topologically quantum error corrected computation" | ||
| readme = "README.md" | ||
| license = { file = "LICENSE" } | ||
| keywords = ["topological quantum error correction", "qec", "detectors"] | ||
| requires-python = ">= 3.9" | ||
| requires-python = ">= 3.10" | ||
| classifiers = [ | ||
| "Development Status :: 4 - Beta", | ||
| "Intended Audience :: Developers", | ||
|
|
@@ -28,12 +28,19 @@ classifiers = [ | |
| "Programming Language :: Python :: 3.11", | ||
| "Programming Language :: Python :: 3.12", | ||
| "Programming Language :: Python :: 3.13", | ||
| "Programming Language :: Python :: 3.14", | ||
| "Topic :: Scientific/Engineering", | ||
| "Topic :: Software Development :: Libraries", | ||
| "Topic :: Utilities", | ||
| "Typing :: Typed", | ||
| ] | ||
| dynamic = ["dependencies"] | ||
| dependencies = [ | ||
| "numpy>=1.22,<2.3", # Upper bound: https://github.com/tqec/tqec/pull/659 | ||
| "numpy>=1.24; python_version >= '3.11'", | ||
| "numpy>=1.26; python_version >= '3.12'", | ||
| "numpy>=2.1; python_version >= '3.13'", | ||
| "stim>=1.15.0", | ||
| ] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is dependabot needed for this repo?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think there are some bots already? But admittedly, the CICD aspect is not as polished as tqec. Let's start by switching to uv, ty, and ruff and make the pyproject and GitHub action configurations more rigorous. |
||
|
|
||
| [project.urls] | ||
| Documentation = "https://tqec.github.io/tqecd/" | ||
|
|
@@ -64,10 +71,6 @@ exclude = ["*test.py"] | |
| [tool.setuptools.package-data] | ||
| tqecd = ["py.typed"] | ||
|
|
||
| [tool.setuptools.dynamic] | ||
| dependencies = { file = "requirements.txt" } | ||
|
|
||
|
|
||
| [tool.mypy] | ||
| # See https://numpy.org/devdocs/reference/typing.html | ||
| plugins = "numpy.typing.mypy_plugin" | ||
|
|
@@ -106,7 +109,7 @@ warn_return_any = true | |
|
|
||
| # See https://mypy.readthedocs.io/en/stable/config_file.html#using-a-pyproject-toml | ||
| [[tool.mypy.overrides]] | ||
| module = ["stim", "pysat", "pysat.solvers"] | ||
| module = ["stim"] | ||
| ignore_missing_imports = true | ||
|
|
||
| [tool.coverage.run] | ||
|
|
||
This file was deleted.
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,150 @@ | ||
| """Provides utility functions to find "covers" of Pauli strings.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from collections.abc import Callable, Iterable | ||
| from typing import Any | ||
|
|
||
| from tqecd.pauli import PauliString | ||
|
|
||
|
|
||
| def _find_cover( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe document somewhere what exactly is meant by "cover"
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Basically the first line of the docstring below. |
||
| target: PauliString, sources: list[PauliString], on_qubits: frozenset[int] | ||
| ) -> list[int] | None: | ||
| """Try to find a set of boundary stabilizers from ``sources`` that generate | ||
| target on qubits ``on_qubits``. | ||
|
|
||
| If multiple valid covers exist, the covers involving the lowest number of | ||
| :class:`~tqecd.pauli.PauliString` instances from ``sources`` are listed, and | ||
| a random cover is picked from that list. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not just return all of the covers?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because there are exponentially many of them, and they don't really make a difference. But this doc describes the previous behavior and is inaccurate now. Will fix. |
||
|
|
||
| Args: | ||
| target: the stabilizers to cover with stabilizers from ``sources``. | ||
| sources: stabilizers that can be used to cover `target`. | ||
| on_qubits: qubits to consider when trying to cover ``target`` with | ||
| ``sources``. | ||
|
|
||
| Returns: | ||
| Either a list of indices over ``sources`` that, when combined, cover | ||
| exactly the provided ``target`` on all the qubits provided in | ||
| ``on_qubits``, or ``None`` if such a list could not be found. | ||
| """ | ||
| return _solve_linear_system( | ||
| _construct_basis({}, sources, lambda s: s.to_int(on_qubits)), | ||
| target.to_int(on_qubits), | ||
| ) | ||
|
|
||
|
|
||
| def find_exact_cover( | ||
| target: PauliString, sources: list[PauliString] | ||
| ) -> list[int] | None: | ||
| """Try to find a set of pauli strings from ``sources`` that generate exactly | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. *Pauli strings |
||
| ``target``. | ||
|
|
||
| The Pauli strings returned (via indices over the provided ``sources``), once | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By the by, I noticed #47 , are you feeling the slowdown in this computation too?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This computation doesn't rely on the |
||
| multiplied together, should be exactly equal to ``target``. In particular, the | ||
| following post-condition should hold: | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| target = None # to replace | ||
| sources = [None] # to replace | ||
| cover_indices = find_exact_cover(target, sources) | ||
| resulting_pauli_string = PauliString({}) | ||
| for i in cover_indices: | ||
| resulting_pauli_string = resulting_pauli_string * sources[i] | ||
| assert resulting_pauli_string == target, "Should hold" | ||
|
|
||
| Args: | ||
| target: the stabilizers to cover with stabilizers from ``sources``. | ||
| sources: stabilizers that can be used to cover ``target``. | ||
|
|
||
| Returns: | ||
| Either a list of indices over ``sources`` that, when combined, cover | ||
| exactly the provided ``target``, or ``None`` if such a list could not be | ||
| found. | ||
| """ | ||
| # If target is the identity, pick no Pauli string. | ||
| # Note: we might want to disallow an empty return in the future. | ||
| if target.non_trivial_pauli_count == 0: | ||
| return [] | ||
|
|
||
| # Else, if there are no sources, we cannot find a solution. | ||
| if not sources: | ||
| return None | ||
|
|
||
| # We want an exact (i.e., equality) cover on all qubits, to be sure that | ||
| # the post-condition in the docstring holds. For that, it is sufficient to | ||
| # only consider all the qubits where either `target` or at least one of the | ||
| # items of `sources` acts non-trivially (i.e., something else than the identity). | ||
| involved_qubits = frozenset(target.qubits) | ||
| for source in sources: | ||
| involved_qubits |= frozenset(source.qubits) | ||
|
|
||
| return _find_cover(target, sources, involved_qubits) | ||
|
|
||
|
|
||
| def find_commuting_cover_on_target_qubits( | ||
| target: PauliString, sources: list[PauliString] | ||
| ) -> list[int] | None: | ||
| """Try to find a set of boundary stabilizers from ``sources`` that generate a | ||
| superset of ``target``. | ||
|
|
||
| This function try to find a set of Pauli strings from ``sources`` that | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. typo: "This function tries to find ..." |
||
| includes ``target`` (i.e., on every qubit where ``target`` is non-trivial, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is a trivial target?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Non-identity |
||
| the product of each of the returned Pauli strings should commute with | ||
| ``target``). | ||
|
|
||
| The differences with :func:`find_cover` are: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The method names are somewhat legacy from the previous implementation. Now after simplification, it's clear that they only differ by how they treat the involved qubits and edge cases. I will think about whether the naming could be improved. |
||
|
|
||
| 1. this function does not restrict the output of the product of each of | ||
| the returned Pauli string on qubits where ``target`` acts trivially (i.e. | ||
| "I"). So in practice, on qubits where ``target[qubit] == "I"``, the value | ||
| of the returned Pauli string can be anything. | ||
| 2. this function does not restrict the output of the product of each of | ||
| the returned Pauli string to exactly match ``target`` on qubits where | ||
| it acts non-trivially, but rather requires the output to commute with | ||
| ``target`` on those qubits. | ||
|
|
||
| Args: | ||
| target: the stabilizers to cover with stabilizers from ``sources``. | ||
| sources: stabilizers that can be used to cover ``target``. | ||
|
|
||
| Returns: | ||
| Either a list of a stabilizers that, when combined, commute with | ||
| the provided ``target``, or ``None`` if such a list could not be found. | ||
| """ | ||
| if not sources: | ||
| return None | ||
| return _find_cover(target, sources, frozenset(target.qubits)) | ||
|
|
||
|
|
||
| def _solve_linear_system( | ||
| basis: dict[int, tuple[int, int]], x: int, update_basis: bool = True | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might want to define a |
||
| ) -> list[int] | None: | ||
| """Gaussian elimination over GF(2).""" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd add a little more detail to this docstring or add a few more comments ot the code since it's kind of abstract. Like, what the valid inputs are (since it can return None).
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It should also document how the method mutates the basis. |
||
| mask = 1 << len(basis) | ||
| while x: | ||
| highest_bit = x.bit_length() - 1 | ||
| if highest_bit not in basis: | ||
| if update_basis: | ||
| basis[highest_bit] = (x, mask) | ||
| return None | ||
| pivot, pivot_mask = basis[highest_bit] | ||
| x ^= pivot | ||
| mask ^= pivot_mask | ||
| return _int_to_bit_indices(mask)[:-1] | ||
|
|
||
|
|
||
| def _int_to_bit_indices(x: int) -> list[int]: | ||
| """Convert an integer to a list of indices where the bits are set.""" | ||
| return [i for i in range(x.bit_length()) if (x >> i) & 1] | ||
|
|
||
|
|
||
| def _construct_basis( | ||
| basis: dict[int, tuple[int, int]], items: Iterable[Any], func: Callable[[Any], int] | ||
| ) -> dict[int, tuple[int, int]]: | ||
| """Construct a linear basis from the given items using the provided function.""" | ||
| for item in items: | ||
| _solve_linear_system(basis, func(item)) | ||
| return basis | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to do a release?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. Notice the reduced dependencies and the further implications.