Skip to content

Cyclic Hypergraph Product Codes - #431

Merged
perlinm merged 4 commits into
qLDPCOrg:mainfrom
michaelontiveros:cyclic
Apr 4, 2026
Merged

Cyclic Hypergraph Product Codes#431
perlinm merged 4 commits into
qLDPCOrg:mainfrom
michaelontiveros:cyclic

Conversation

@michaelontiveros

@michaelontiveros michaelontiveros commented Apr 2, 2026

Copy link
Copy Markdown
Collaborator

This adds classical CyclicCode and quantum CHGPCode, CRCode.
This closes issue #400.

@perlinm perlinm left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the contribution! This mostly looks good to me. I have just one proposal regarding the definition of a CHGPCode rather than a C2Code.

Otherwise, I sprinkled some notes for myself to address in a future PR.

Comment on lines +78 to +86
# Circulant matrix Q.
Id = galois.GF(field or DEFAULT_FIELD_ORDER).Identity(bits)
Q = Id[:, (np.arange(bits) - 1) % bits]

# poly(Q).
matrix = 0 * Id
for term in poly.as_terms()[0]:
coeff, exponent = term[1][0][0], term[1][1][0]
matrix += int(coeff) * np.linalg.matrix_power(Q, exponent)

@perlinm perlinm Apr 2, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some thoughts (out of scope for this PR):

This is pretty clean, but I worry there may be some edge cases it does not catch. I struggled a lot with edge cases for the construction of codes.QCCodes, here, though honestly:

  • my code there is likely over-engineered, and could use detailed review/refactor by somebody more deeply familiar with sympy, and
  • qldpc should factor out the code that converts a Sympy polynomial into an abstract.RingMember, moving it to abstract.py (probably inside the RingMember class), and re-use that code here. I'll leave that for a future PR, though.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added #432, which we should be able to use here. I'm happy to make that change in a follow-up PR, though.

Comment on lines +42 to +43
with pytest.raises(ValueError, match="not a polynomial"):
codes.CyclicCode(3, 4)

@perlinm perlinm Apr 2, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor notes for myself (for me to address in a follow-up PR):

  1. Arguably 4 is a trivial polynomial that would get lifted to an identity matrix (or rather, a multiple thereof), so I would sooner raise the error here that the polynomial is not univariate.
  2. Leaning into the interpretation of 4 as a trivial polynomial, another error that could be thrown here is that 4 is not a member of GF(3). However, if p is prime, then every integer has an unambiguous interpretation as an element of GF(p) that we should arguably allow (namely, c --> c % p). I think the conversion of polynomials into RingMembers (currently buried in QCCode) does not support coefficients c that don't satisfy 0 <= c < p, in which case we should modify the conversion to support more integers. The conversion should likewise allow for negative integers (for non-prime fields), if it does not already.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding point (2) above, this is now dealt with in #432.

Comment thread src/qldpc/codes/quantum.py Outdated
Comment on lines +1189 to +1206
class C2Code(HGPCode):
"""Symmetric cyclic hypergraph product code.

A C2Code is a hypergraph square of a CyclicCode.

References:
- Definition 2 of https://arxiv.org/pdf/2511.09683
"""

def __init__(self, bits: int, poly: sympy.Basic, field: int | None = None) -> None:
"""Construct a C2Code from a block length and a polynomial in one variable."""

code = CyclicCode(bits, poly, field)

super().__init__(code, code, field)


class CRCode(HGPCode):

@perlinm perlinm Apr 2, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a naming "almost collision" with C4Code and C6Code, which are fixed codes rather than code families. Also, what about CxC codes?

I would propose we define a CHGPCode that accepts poly_a: sympy.Basic, poly_b: sympy.Basic | None = None. If poly_b is None, then poly_b is set to poly_a, making it a C2 code. There now need to be two block lengths, though, so maybe we can provide a bits: int | tuple[int, int] input to likewise determine the block length of both cyclic codes.

CRCode is different enough that it should probably kept as is.

@perlinm
perlinm self-requested a review April 3, 2026 03:21

@perlinm perlinm left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me!

@perlinm
perlinm merged commit 9edabdc into qLDPCOrg:main Apr 4, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants