-
Notifications
You must be signed in to change notification settings - Fork 51
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
Lifted and lifted product codes #312
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #312 +/- ##
==========================================
- Coverage 82.97% 82.92% -0.05%
==========================================
Files 61 64 +3
Lines 4023 4171 +148
==========================================
+ Hits 3338 3459 +121
- Misses 685 712 +27 ☔ View full report in Codecov by Sentry. |
|
@Krastanov please help review this PR, thanks! |
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.
This is shaping up neatly, thank you!
I left in a lot of comments but the overarching concerns are:
- most importantly, better documentation, and potentially reworking the user-facing API, as currently a user will have a lot of difficulty creating a code
- a lot of custom code that might break if Nemo has a breaking release (they do that often)
- better test coverage
For documenting fields of structs, I would suggest using the docstringhelpers package. See for instance here: https://github.com/QuantumSavory/QuantumSavory.jl/blob/master/src/ProtocolZoo/switches.jl#L139-L157
@@ -16,10 +16,12 @@ export parity_checks, parity_checks_x, parity_checks_z, iscss, | |||
code_n, code_s, code_k, rate, distance, | |||
isdegenerate, faults_matrix, | |||
naive_syndrome_circuit, shor_syndrome_circuit, naive_encoding_circuit, | |||
RepCode, | |||
PermGroupRing, PermutationGroupRing, PermGroupRingElem, cyclic_permutation, permutation_repr, # group utils |
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.
We should not be exporting all these fairly internal utilities, at least not until a few more versions come out and we are sure we are sticking to them.
Classical codes lifted over a permutation group ring [panteleev2021degenerate](@cite) [panteleev2022asymptotically](@cite). | ||
|
||
- `A::Matrix`: the base matrix of the code, whose elements are in a permutation group ring. | ||
- `repr::Function`: a function that converts a permutation group ring element to a matrix; |
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.
I am surprised this needs a general function. Is there any situation where we would need something besides the default?
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.
I leave this because the default permutation (cyclic) matrix representation we provided is not necessarily the most efficient one. There are cases we can find a representation that need fewer qubit number. So I want to allow the customization.
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.
Could you give more specific examples? Permitting an arbitrary function in a situation like this is generally a sign of missing abstraction, especially in a language like Julia where you have access to multimethods.
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.
Let's consider a simple case: D4 group. The wiki.
In our default way, the representation will be 8x8 matrices, where 8 is the group's order.
But we can find a 4x4 matrix representation for the group. We can start with its representation on integer matrix space:
What we need is binary matrices; therefore, we can replace "1" with the 2x2 identity matrix, "0" with the 2x2 zero matrices, and "-1" with the Pauli X matrix. After replacement, we will get a 4x4 matrix representation.
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.
Examples like this should probably be part of the documentation. Otherwise the user would be pretty confused how to approach these extra capabilities.
Permutation group ring over Prime field of characteristic 2 | ||
|
||
julia> f0 = R(0) | ||
Dict{Perm, Nemo.FqFieldElem}() |
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.
Why is this printing as a dictionary?
julia> f2 = R(Perm([1,3,2])) | ||
Dict{Perm{Int64}, Nemo.FqFieldElem}((2,3) => 1) | ||
|
||
julia> f3 = R(Dict(Perm(3) => 1, Perm([3,1,2]) => 1)) |
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.
what it means to call this on a dictionary
|
||
|
||
""" | ||
Lifted product codes [panteleev2021degenerate](@cite) [panteleev2022asymptotically](@cite) |
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.
I think a lot of my concerns expressed elsewhere can be addressed by being a bit more detailed with what these are, something like an extra paragraph expounding upon: these are the lifted product codes (cite) which are a generalization of the general bicycle codes (cite) which are a generalization of the bicycle code (cite).
Then we can have something like: the bicycle code is defined by such and such set and if you have that set you can convert it to the necessary representation and use it here by doing so and so.
The generalized bicycle code is defined by a circulant matrix and you can use a circulant matrix to create a code here by doing yada yada
Finally, the most general form is yada yada where you need to provide so and so.
One of my worries is that currently I do not know how to use what you have implemented to set up the generalized bicycle codes from https://quantum-journal.org/papers/q-2021-11-22-585/pdf/
See footnote 2 on page 2 of https://quantum-journal.org/papers/q-2021-11-22-585/pdf/
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.
related to my post from a minute ago: I think this is an important question
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.
Regarding the concern you have on generalized bicycle codes, I think we can construct them using 1x1 matrices. For example, we consider the first example in Appendix B (page 20) of https://quantum-journal.org/papers/q-2021-11-22-585/pdf/,
using my implementation,
l = 127
R = PermutationGroupRing(GF(2), l)
x = R(cyclic_permutation(1, l))
A = [1 + x^15 + x^20 + x^28 + x^66]
B = [1 + x^58 + x^59 + x^100 + x^121]
LPCode(LiftedCode(A), LiftedCode(B))
The current documentation is indeed so bad for these special cases. I will improve the document by adding these cases.
""" | ||
Classical codes lifted over a permutation group ring [panteleev2021degenerate](@cite) [panteleev2022asymptotically](@cite). | ||
|
||
- `A::Matrix`: the base matrix of the code, whose elements are in a permutation group ring. |
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.
Looking at the end of this list of changes, it seems like you are imagining the user doing a couple of different things to generate codes, but it does not seem like the starting point is really this matrix.
Can we structure it in a way that is simpler, so that the user just gives what would be the natural object for them to work with. Especially looking at the test file, it does not seem like you expect most users to start with such a matrix. Here are a few suggestions, probably some of them are bad, maybe all of them are bad, but some discussion of making this API easier to use is needed:
- maybe a constructor that just takes a dictionary of permutation and does all the conversions behind the scene
- a constructor that just takes the circulant matrices or maybe a separate struct for simpler instances of these codes (bicycle codes and generalized bicycle codes)
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.
related to my post from a minute ago: I think this is an important question
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.
In #312 (comment), I proposed two new constructors: one from Matrix{Perm}
, the other from Matrix{Int}
.
As for the suggestion here,
maybe a constructor that just takes a dictionary of permutation and does all the conversions behind the scene
I agree it will be good to have this.
a constructor that just takes the circulant matrices or maybe a separate struct for simpler instances of these codes (bicycle codes and generalized bicycle codes)
I think taking circulant matrices is mathematically the same as taking Matrix{Int}
, the meaning of which is explained in #312 (comment). And the latter would be more efficient.
As for 2GBA codes, including bicycle codes and generalized bicycle codes, the only difference is the matrix will be 1x1. It looks to me that having something like LiftedCode(::Perm)
might be slightly confusing, although more convenient.
If we make a separate struct, then seemingly everything other than new constructors will be the same as LiftedCode
. We can choose to re-implement and simplify the production construction. But it is not very appealing to me.
Perhaps it makes more sense to provide some functions (but not make them constructors) to construct these simpler codes, which can still be in the LPCode
type.
A::Matrix{PermGroupRingElem} | ||
repr::Function |
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.
You are already hardcoding the use of your PermGroupRingElem
, so we can probably be a bit neater about repr
. Instead of asking for a repr here, you can ask for a new method of permutation_repr that specializes on the group type.
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.
(This seems to be related to your comments on the repr
in the docstring. There #312 (comment), I left a comment about why I think other-than-default representations will be useful.)
Regarding the suggestion here, I think it assumes that users would define their own group ring type in analogy to PermGroupRing
, which can be done in principle but requires too much work. I do not expect most users to do that. And if they still use our PermGroupRing
, then they have to redefine the method and cover the default one, which looks more confusing to me. Also, there is the possibility that one wants to use different representations for the same group type in different codes. In that case, the methods will conflict.
Instead, the current implementation assumes that users need to customize matrix representation for their own group and still want to use our PermGroupRing
. The logic behind this is that one can always use the permutation group to represent any other group, which is the way OScar.jl
does, for example. In this way, PermGroupRing
can represent the specialized group, and we ask for a customized function that may better deal the specialized group.
The general function here also looks bad for me. But with these considerations, I do not have better ideas. I would appreciate your input on this.
Maybe a way is to define parity_checks(c::LiftedCode; repr)
and keep at least the LiftedCode
type clearly defined if we accept that a code can have various parity checks.
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.
My reasoning starts from the following point: you are asking the user of this structure to provide an "unlifted" matrix and a lifting function, but the only thing you do with those two entities is to calculate the "lifted" matrix. That is something the user could have already done themselves and then they just provide us with the lifted matrix directly. We do not do anything sophisticated with the abstraction created here, so that abstraction probably should not exist (because the abstraction does not simplify anything for the user -- they still need to know what the unlifted matrix is and what the lifting function is, and now they also need to learn this library-specific abstraction).
Obviously, my description is a bit willfully negative, but this is a design principle on which I believe we should be leaning hard.
Then there is a secondary issue that by just asking for a function without specifying what interface that function should have, we are making this too unconstrained and error-prone.
Lastly, I am confused when you are saying that the user will define their own type in analogy to the one you have created. If they do so, they would not be able to use this structure, because the matrix element type is hardcoded.
Defining a parity_checks(c::LiftedCode; repr)
would be a bit of a "pun" in Julia parlance. It creates a method for a function that does not follow the structure expected from all other methods, making composability a bit more difficult.
I am still leaning towards avoiding using the new type you have created (I have a lot of questions related to it elsewhere through this PR -- answering them would help me understand why such a custom type is necessary, instead of just using Nemo/Oscar as is). Independently of whether we are relying on the new custom type, setting up a lift
function and asking the user to define methods for it seems cleaner to me (with a default method provided by us).
But I might be completely wrong about this. I would not know that until I have more information about how the user is expected to use this code. I have a few more questions in this PR that when answered I will have a better idea. Look at the questions about "how would the user use this to represent common types of codes". I will mark them up again now.
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.
Thanks for these comments! I replied to the questions that you marked (and #312 (comment) and #312 (comment)), which hopefully clarified some important points that I have not documented well.
For a short answer to "why such a custom type is necessary" is that we need something with multiplication and addition, where the multiplication is defined by permutation group.
Before writing my type, I checked the types in Nemo and Oscar. But I did not find any of them suitable. There, they have rings that do not have group multiplication and the permutation group that does not have addition. What I have implemented is basically a composition of both.
struct LPCode <: AbstractECC | ||
c₁::LiftedCode | ||
c₂::LiftedCode | ||
repr::Function |
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.
I just noticed this, but why does LPCode have a repr function if the LiftedCode structures inside of it have one already as well?
Thanks for all the answers you have provided -- they help in wrapping my head around your work. In terms of TODOs, besides the things you are already taking care of, I think we need the following:
|
You mentioned that you have some pros and cons in mind about whether to make helper functions, or constructors, or new types for all these "simpler" versions of LP codes -- please proceed with what you believe is most reasonable and do not worry if I happen to use the word "constructor" when you were thinking about a "helper function" |
Thanks for your detailed suggestions! I think I am clear about these TODOs and plan to complete them in the next couple of days. |
closing now that #356 is merged |
Add lifted codes, lifted product (LP) codes, and group ring functionality, which is essential for these codes.
Note: The added LP codes trigger errors in syndrome circuits, as reported in #306. So, decoding tests only include commutativity checks at the moment. For the same reason, I also skip LP codes in
all_testablable_code_instances
.